diff --git a/.eslintrc.js b/.eslintrc.js
index 2866ad7..8cdce03 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -63,6 +63,20 @@
         'property': 'exportPath',
         'message': 'Use ES modules or cr.define() instead',
       },
+      {
+        'object': 'MockInteractions',
+        'property': 'tap',
+        'message': 'Do not use on-tap handlers in prod code, and use the ' +
+            'native click() method in tests. See more context at ' +
+            'crbug.com/812035.',
+      },
+      {
+        'object': 'test',
+        'property': 'only',
+        'message': 'test.only() silently disables other tests in the same ' +
+            'suite(). Did you forget deleting it before uploading? Use ' +
+            'test.skip() instead to explicitly disable certain test() cases.',
+      },
     ],
     'no-restricted-syntax': ['error', {
       'selector': 'CallExpression[callee.object.name=JSON][callee.property.name=parse] > CallExpression[callee.object.name=JSON][callee.property.name=stringify]',
@@ -300,7 +314,10 @@
   // We do not allow per-directory custom eslint rules. This section exists for
   // rules that are in the process of being applied to the whole code base.
   {
-    'files': ['content/browser/resources/**/*.[jt]s',
+    'files': ['chrome/browser/resources/**/*.[jt]s',
+              'chrome/test/data/pdf/**/*.ts',
+              'chrome/test/data/webui/**/*.[jt]s',
+              'content/browser/resources/**/*.[jt]s',
               'ui/webui/resources/**/*.[jt]s',],
     'rules': {
       'eqeqeq': ['error', 'always', {'null': 'ignore'}],
@@ -316,6 +333,52 @@
       'no-restricted-syntax': 'off',
     },
   },
+
+  // 1-month exception for //chrome/browser/resources/ash/settings. This can be
+  // removed November 15 2024.
+  {
+    'files' : ['chrome/browser/resources/ash/settings/**/*.[jt]s'],
+    // Disable clang-format because it produces odd formatting for these rules.
+    // clang-format off
+    'rules' : {
+      // Disable due to large number of violations in this folder.
+      '@typescript-eslint/consistent-type-imports': 'off',
+      /**
+       * https://google.github.io/styleguide/tsguide.html#return-types
+       * The Google TS style guide makes no formal rule on enforcing explicit
+       * return types. However, explicit return types have clear advantages in
+       * both readability and maintainability.
+       */
+      '@typescript-eslint/explicit-function-return-type': [
+        'error',
+        {
+          // Function expressions are exempt.
+          allowExpressions: true,
+          // Avoid checking Polymer static getter methods.
+          allowedNames: ['is', 'template', 'properties', 'observers'],
+        },
+      ],
+      /**
+       * https://google.github.io/styleguide/tsguide.html#type-inference
+       */
+      '@typescript-eslint/no-inferrable-types': [
+        'error',
+        {
+          // Function parameters may have explicit types for clearer APIs.
+          ignoreParameters: true,
+          // Class properties may have explicit types for clearer APIs.
+          ignoreProperties: true,
+        },
+      ],
+      /**
+       * https://google.github.io/styleguide/tsguide.html#function-expressions
+       */
+      'prefer-arrow-callback': 'error',
+      'quote-props': ['error', 'consistent-as-needed'],
+    },
+    // clang-format on
+  },
+
   ],
 
   'ignorePatterns': [
@@ -324,10 +387,6 @@
     'chrome/browser/resources/gaia_auth_host/password_change_authenticator.js',
     'chrome/browser/resources/gaia_auth_host/saml_username_autofill.js',
 
-    // Ignore because of https://crbug.com/1033337
-    'chrome/test/data/webui/chromeos/async_gen.js',
-    'chrome/test/data/webui/chromeos/cr_focus_row_behavior_interactive_test.js',
-    'chrome/test/data/webui/chromeos/**/*_browsertest.js',
 
     // No point linting auto-generated files.
     'tools/typescript/definitions/**',
@@ -338,5 +397,22 @@
     // latest eslint. See https://crbug.com/368085620.
     'ash/webui/camera_app_ui/resources/**',
     'ash/webui/recorder_app_ui/resources/**',
+
+    // ESLint is disabled for directories that use custom linting rules, which
+    // is no longer supported.
+    // TODO(https://crbug.com/369766161): Bring directories into conformance to
+    // re-enable linting.
+    'ash/webui/**',
+    'chrome/browser/resources/ash/**/*.[jt]s',
+    'chrome/browser/resources/chromeos/**',
+    'chrome/test/data/webui/chromeos/**',
+
+    // TODO(https://crbug.com/41446521): Bring extension test files into
+    // conformance.
+    'chrome/test/data/extensions/**',
+
+    // TODO(https://crbug.com/370730323): 1-month exception. This can be removed
+    // in November 2024.
+    '!chrome/browser/resources/ash/settings/**',
   ]
 };
diff --git a/DEPS b/DEPS
index c3284ca..e706a583 100644
--- a/DEPS
+++ b/DEPS
@@ -285,7 +285,7 @@
   # 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': '5fb36dd08a257623ee0738747286de09662e4591',
+  'skia_revision': '0411eaf35f69e2a74415775eddab18aa00048ba8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -293,7 +293,7 @@
   # 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': 'cc44090d3483efdbae0dacf9c3fdb6c5d5a950fa',
+  'angle_revision': '80e5e611e4b28d33c0c0359e7654a02703aead46',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -305,7 +305,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
-  'boringssl_revision': 'f8bb652b01d3b34a20ddbaaa35def260783ee734',
+  'boringssl_revision': '81345b84505e9c23c156b2c7a1e655a204bd3e9a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
@@ -357,11 +357,11 @@
   # 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': 'efc3087b5910f58d031f021f7893034b8a1e18db',
+  'chromium_variations_revision': '577d77d5fd18c1e1424568bcdd1b9f24c43a2c10',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
-  'crossbench_revision': 'de2d0bbb34999a3266e0c02af50ca51a1480cd4d',
+  'crossbench_revision': '463368dff43c6f455ecaaf764d8e8f7a96764107',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -377,7 +377,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': 'b40634d52b0f5dcde839e73c8bdac4909458217a',
+  'devtools_frontend_revision': '0f75c875986acc3c538dbc61b9f7aa08d894774e',
   # 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.
@@ -481,7 +481,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ffmpeg
   # and whatever else without interference from each other.
-  'ffmpeg_revision': '30735bb16a66e84d6324b5858eef314822b6d419',
+  'ffmpeg_revision': '686d6944501a6ee9c849581e3fe343273d4af3f6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling beto-core
   # and whatever else without interference from each other.
@@ -1750,7 +1750,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '84c1220a80b203163a2c3d124ca103f63580d8ce',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '60dadf73f7a0b2353c81d3826b1ee840f57eb0bf',
     'condition': 'checkout_src_internal',
   },
 
@@ -2242,7 +2242,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '226197a61ac2b08b3860b5c73f8411ba0ba43947',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '136de5ccd7163261020064db944bb07bf5f5cf12',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -2576,10 +2576,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '1b6371436a0a60e6b9a4ae2a40a8eba198e3af02',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '9d029d337ae1832b5a7b9bf049a87076c12f749d',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'c42a89c461fb3c6c375f2d1e09275ed9f99d3ddf',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '076eb6cdf236cd6125ab126df2340ca3ee265425',
+    Var('webrtc_git') + '/src.git' + '@' + '12f15e99c274efe531a31ceffcc5edb6b1111cf3',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -4122,7 +4122,7 @@
 
   'src/chrome/browser/platform_experience/win': {
       'url': Var('chrome_git') + '/chrome/browser/platform_experience/win.git' + '@' +
-        'f8bbf7f750c82f7463cebabcaee82182b709859f',
+        '6b3b72a0536bca7d403ed0439ddf9f434e076191',
       'condition': 'checkout_src_internal',
   },
 
@@ -4241,7 +4241,7 @@
 
   'src/chrome/updater/internal': {
     'url': Var('chrome_git') + '/chrome/updater/internal.git' + '@' +
-        '11f83f25bbbd11d51c661f39e0cda7e6d7d2bada',
+        'e8e0e06cc5b990d06b4583886f0f8946241fb221',
     'condition': 'checkout_src_internal',
   },
 
@@ -4276,7 +4276,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        '150801e3570d70b31bac149381371b0ae6326666',
+        'b359dced1380042078bf7191e24a4b9bf1c5d61e',
       'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 5254486..85fbd6f 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -977,10 +977,10 @@
     "login/ui/login_user_view.h",
     "login/ui/management_bubble.cc",
     "login/ui/management_bubble.h",
+    "login/ui/management_disclosure_dialog.cc",
+    "login/ui/management_disclosure_dialog.h",
     "login/ui/management_disclosure_field_trial.cc",
     "login/ui/management_disclosure_field_trial.h",
-    "login/ui/management_disclosure_view.cc",
-    "login/ui/management_disclosure_view.h",
     "login/ui/non_accessible_view.cc",
     "login/ui/non_accessible_view.h",
     "login/ui/note_action_launch_button.cc",
@@ -3402,6 +3402,7 @@
     "//chromeos/ash/components/human_presence",
     "//chromeos/ash/components/login/login_state",
     "//chromeos/ash/components/metrics",
+    "//chromeos/ash/components/mojo_service_manager",
     "//chromeos/ash/components/multidevice/logging",
     "//chromeos/ash/components/network",
     "//chromeos/ash/components/peripheral_notification",
@@ -4633,6 +4634,7 @@
     "//chromeos/ash/components/login/auth:test_support",
     "//chromeos/ash/components/login/login_state",
     "//chromeos/ash/components/metrics",
+    "//chromeos/ash/components/mojo_service_manager",
     "//chromeos/ash/components/network:test_support",
     "//chromeos/ash/components/osauth/test_support",
     "//chromeos/ash/components/phonehub:test_support",
diff --git a/ash/ambient/ambient_weather_controller.cc b/ash/ambient/ambient_weather_controller.cc
index 8a3ff0b..b18d69b7 100644
--- a/ash/ambient/ambient_weather_controller.cc
+++ b/ash/ambient/ambient_weather_controller.cc
@@ -134,7 +134,6 @@
       ->ambient_backend_controller()
       ->FetchWeather(
           /*weather_client_id=*/std::nullopt,
-          /*prefer_alpha_endpoint=*/false,
           base::BindOnce(
               &AmbientWeatherController::StartDownloadingWeatherConditionIcon,
               weak_factory_.GetWeakPtr()));
diff --git a/ash/ambient/backdrop/ambient_backend_controller_impl.cc b/ash/ambient/backdrop/ambient_backend_controller_impl.cc
index 0a55431..9aa946a156 100644
--- a/ash/ambient/backdrop/ambient_backend_controller_impl.cc
+++ b/ash/ambient/backdrop/ambient_backend_controller_impl.cc
@@ -446,7 +446,6 @@
 
 void AmbientBackendControllerImpl::FetchWeather(
     std::optional<std::string> weather_client_id,
-    bool prefer_alpha_endpoint,
     FetchWeatherCallback callback) {
   auto response_handler =
       [](FetchWeatherCallback callback,
@@ -478,8 +477,7 @@
   DCHECK(user->HasGaiaAccount());
   BackdropClientConfig::Request request =
       backdrop_client_config_.CreateFetchWeatherInfoRequest(
-          user->GetAccountId().GetGaiaId(), GetClientId(), weather_client_id,
-          prefer_alpha_endpoint);
+          user->GetAccountId().GetGaiaId(), GetClientId(), weather_client_id);
   std::unique_ptr<network::ResourceRequest> resource_request =
       CreateResourceRequest(request);
   auto backdrop_url_loader = std::make_unique<BackdropURLLoader>();
diff --git a/ash/ambient/backdrop/ambient_backend_controller_impl.h b/ash/ambient/backdrop/ambient_backend_controller_impl.h
index d33532508..6fdab6e 100644
--- a/ash/ambient/backdrop/ambient_backend_controller_impl.h
+++ b/ash/ambient/backdrop/ambient_backend_controller_impl.h
@@ -42,7 +42,6 @@
       int num_albums,
       OnSettingsAndAlbumsFetchedCallback callback) override;
   void FetchWeather(std::optional<std::string> weather_client_id,
-                    bool prefer_alpha_endpoint,
                     FetchWeatherCallback callback) override;
   const std::array<const char*, 2>& GetBackupPhotoUrls() const override;
   std::array<const char*, 2> GetTimeOfDayVideoPreviewImageUrls(
diff --git a/ash/birch/OWNERS b/ash/birch/OWNERS
index 3cac2795..1f689b7a 100644
--- a/ash/birch/OWNERS
+++ b/ash/birch/OWNERS
@@ -1,2 +1,10 @@
 mmourgos@chromium.org
 tbarzic@chromium.org
+
+per-file *birch_coral_item*=sammiequon@chromium.org
+per-file *birch_coral_item*=yulunwu@chromium.org
+per-file *birch_coral_item*=zxdan@chromium.org
+per-file *birch_coral_grouped_icon_image*=sammiequon@chromium.org
+per-file *birch_coral_grouped_icon_image*=yulunwu@chromium.org
+per-file *birch_coral_grouped_icon_image*=zxdan@chromium.org
+per-file *coral_item_remover*=yulunwu@chromium.org
diff --git a/ash/birch/birch_coral_provider.cc b/ash/birch/birch_coral_provider.cc
index 4598cc5a..f95e1245 100644
--- a/ash/birch/birch_coral_provider.cc
+++ b/ash/birch/birch_coral_provider.cc
@@ -261,27 +261,30 @@
 
 void BirchCoralProvider::HandleCoralResponse(
     std::unique_ptr<CoralResponse> response) {
+  std::vector<BirchCoralItem> items;
+  groups_.clear();
   if (!response) {
+    LOG(ERROR) << "Failed to receive coral response.";
+    response_.reset();
+    Shell::Get()->birch_model()->SetCoralItems(items);
     return;
   }
   // TODO(yulunwu) update `birch_model_`
   response_ = std::move(response);
-  groups_.clear();
   CHECK(HasValidClusterCount(response_->groups().size()));
-  std::vector<BirchCoralItem> items;
-  // TODO(owenzhang): Remove placeholder page_urls.
-  std::vector<GURL> page_urls;
-  page_urls.emplace_back(("https://chromeunboxed.com/"));
-  page_urls.emplace_back(("https://www.unrealengine.com/"));
-  page_urls.emplace_back(("https://godotengine.org/"));
-
-  std::vector<std::string> app_ids;
-  app_ids.emplace_back("lgnggepjiihbfdbedefdhcffnmhcahbm");
-  app_ids.emplace_back("lgnggepjiihbfdbedefdhcffnmhcahbm");
-  app_ids.emplace_back("lgnggepjiihbfdbedefdhcffnmhcahbm");
 
   for (size_t i = 0; i < response_->groups().size(); ++i) {
     groups_[i] = response_->groups()[i].Clone();
+    std::vector<GURL> page_urls;
+    std::vector<std::string> app_ids;
+    for (const auto& entity : groups_[i]->entities) {
+      if (entity->is_tab_url()) {
+        page_urls.push_back(entity->get_tab_url());
+      }
+      if (entity->is_app_id()) {
+        app_ids.push_back(entity->get_app_id());
+      }
+    }
     items.emplace_back(base::UTF8ToUTF16(groups_[i]->title),
                        /*subtitle=*/std::u16string(), page_urls, app_ids,
                        /*cluster_id=*/int(i));
diff --git a/ash/birch/birch_weather_provider.cc b/ash/birch/birch_weather_provider.cc
index a58f3c2..f816415 100644
--- a/ash/birch/birch_weather_provider.cc
+++ b/ash/birch/birch_weather_provider.cc
@@ -100,15 +100,10 @@
 }
 
 void BirchWeatherProvider::FetchWeather() {
-  // Use the prod endpoint by default. This results in the alpha server being
-  // used for canary/dev channel and the prod server being used for beta/stable.
-  const bool prefer_prod_endpoint = base::GetFieldTrialParamByFeatureAsBool(
-      features::kBirchWeather, "prod_weather_endpoint", true);
   Shell::Get()
       ->ambient_controller()
       ->ambient_backend_controller()
       ->FetchWeather("chromeos-system-ui",
-                     /*prefer_alpha_endpoint=*/!prefer_prod_endpoint,
                      base::BindOnce(&BirchWeatherProvider::OnWeatherInfoFetched,
                                     weak_factory_.GetWeakPtr()));
 }
diff --git a/ash/game_dashboard/game_dashboard_main_menu_view.cc b/ash/game_dashboard/game_dashboard_main_menu_view.cc
index 724fe3e..d18e96c 100644
--- a/ash/game_dashboard/game_dashboard_main_menu_view.cc
+++ b/ash/game_dashboard/game_dashboard_main_menu_view.cc
@@ -104,14 +104,20 @@
 constexpr int kTileCornerRadius = 20;
 // Line height for feature tiles with sub-labels
 constexpr int kTileLabelLineHeight = 16;
-// Feature Tile default padding when there are less than 4 Feature Tiles in the
-// Shortcut Tiles Row.
-constexpr gfx::Insets kDefaultTilePadding = gfx::Insets::TLBR(0, 24, 10, 24);
+// Feature Tile default padding when there are 3 Feature Tiles in the
+// Shortcut Tiles row. Also used as the default padding when creating
+// a Feature Tile.
+constexpr gfx::Insets kThreeTilePadding = gfx::Insets::TLBR(0, 24, 10, 24);
+// Feature tile padding when there are 2 Feature Tiles in the Shortcut Tiles
+// row.
+constexpr gfx::Insets kTwoTilePadding = gfx::Insets::TLBR(10, 12, 10, 24);
 // Feature Tile padding when there are 4 Feature Tiles in the Shortcut Tiles
-// Row.
+// row.
 constexpr gfx::Insets kFourTilePadding = gfx::Insets::TLBR(0, 10, 10, 10);
 // Feature Tile Icon Padding.
-constexpr gfx::Insets kTileIconPadding = gfx::Insets::TLBR(12, 8, 4, 8);
+constexpr gfx::Insets kCompactTileIconPadding = gfx::Insets::TLBR(12, 8, 4, 8);
+// Primary Feature Tile Icon Padding.
+constexpr gfx::Insets kPrimaryTileIconPadding = gfx::Insets::TLBR(8, 20, 8, 8);
 // Primary Feature Tile Label Padding.
 constexpr gfx::Insets kPrimaryTileLabelPadding = gfx::Insets::TLBR(0, 0, 0, 15);
 // Clock View Padding.
@@ -166,7 +172,7 @@
   tile->SetLabel(text);
   tile->SetTooltipText(text);
   tile->SetButtonCornerRadius(kTileCornerRadius);
-  tile->SetTitleContainerMargins(kDefaultTilePadding);
+  tile->SetTitleContainerMargins(kThreeTilePadding);
 
   // Default state colors.
   tile->SetBackgroundColorId(cros_tokens::kCrosSysSystemOnBase);
@@ -192,7 +198,7 @@
   views::ImageButton* tile_icon = tile->icon_button();
   if (type == FeatureTile::TileType::kCompact) {
     // Adjust internal spacing.
-    tile_icon->SetProperty(views::kMarginsKey, kTileIconPadding);
+    tile_icon->SetProperty(views::kMarginsKey, kCompactTileIconPadding);
 
     // Adjust line and text specifications.
     tile_label->SetFontList(
@@ -208,7 +214,7 @@
     // Resize the icon and its margins.
     tile_icon->SetPreferredSize(
         gfx::Size(20, tile_icon->GetPreferredSize().height()));
-    tile_icon->SetProperty(views::kMarginsKey, kTileIconPadding);
+    tile_icon->SetProperty(views::kMarginsKey, kPrimaryTileIconPadding);
 
     // Adjust line specifications and enable text wrapping.
     tile_label->SetProperty(views::kMarginsKey, kPrimaryTileLabelPadding);
@@ -1075,14 +1081,15 @@
     screenshot_tile->sub_label()->SetVisible(false);
   }
 
-  // Shortcut tiles row holds up to 4 tiles, and always contains the
-  // 'toolbar_tile' and the 'screenshot_tile'. If there are 4 tiles in the row,
-  // the padding is set to 'kFourTilePadding', otherwise, the padding is set to
-  // 'kDefaultTilePadding'.
-  const auto title_container_margin = (game_controls_tile_ && record_game_tile_)
-                                          ? kFourTilePadding
-                                          : kDefaultTilePadding;
-  for (auto tile : container->children()) {
+  // Shortcut Tiles row holds up to 4 tiles. Set the padding accordingly to
+  // the amount of tiles in the Shortcut Tiles row.
+  auto tiles = container->children();
+  const auto tile_count = tiles.size();
+  DCHECK(tile_count >= 2 && tile_count <= 4);
+  const auto title_container_margin = tile_count == 4   ? kFourTilePadding
+                                      : tile_count == 3 ? kThreeTilePadding
+                                                        : kTwoTilePadding;
+  for (auto tile : tiles) {
     // Ensure that the Feature Tiles stretch out to equal width and height in
     // the Feature Tile row.
     tile->SetPreferredSize(gfx::Size(1, tile->GetPreferredSize().height()));
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 9b29dc5..1b2cd3b8 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -65,6 +65,7 @@
 #include "ash/system/status_area_widget_delegate.h"
 #include "ash/system/tray/system_tray_notifier.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/logging.h"
@@ -290,8 +291,7 @@
     LockScreen::ScreenType screen_type,
     LoginDataDispatcher* data_dispatcher,
     std::unique_ptr<LoginDetachableBaseModel> detachable_base_model)
-    : NonAccessibleView(),
-      screen_type_(screen_type),
+    : screen_type_(screen_type),
       data_dispatcher_(data_dispatcher),
       detachable_base_model_(std::move(detachable_base_model)) {
   data_dispatcher_->AddObserver(this);
@@ -339,11 +339,6 @@
   bottom_status_indicator_->SetLayoutManager(
       std::move(bottom_status_indicator_layout));
 
-  // TODO(b/330527825): Implement the management disclosure client that will be
-  // used to display the new disclosure.
-  // SetManagementDisclosureClient(
-  // Shell::Get()->login_screen_controller()->GetManagementDisclosureClient());
-
   std::string enterprise_domain_manager = Shell::Get()
                                               ->system_tray_model()
                                               ->enterprise_domain()
@@ -567,17 +562,36 @@
   Shell::Get()->login_screen_controller()->ShowParentAccessButton(false);
 }
 
+void LockContentsView::ShowManagementDisclosureDialog() {
+  if (management_disclosure_dialog_) {
+    // Do not create another dialog if one already exists.
+    return;
+  }
+
+  auto* dialog = new ManagementDisclosureDialog(
+      Shell::Get()->login_screen_controller()
+                  ->GetManagementDisclosureClient() != nullptr
+          ? Shell::Get()
+                ->login_screen_controller()
+                ->GetManagementDisclosureClient()
+                ->GetDisclosures()
+          : std::vector<std::u16string>(),
+      base::BindOnce(
+          [](base::WeakPtr<ManagementDisclosureDialog> dialog) {
+            dialog.reset();
+          },
+          management_disclosure_dialog_));
+  // Save the dialog so it doesn't go out of scope before it is
+  // used and closed.
+  management_disclosure_dialog_ = dialog->GetWeakPtr();
+}
+
 void LockContentsView::SetHasKioskApp(bool has_kiosk_apps) {
   has_kiosk_apps_ = has_kiosk_apps;
 
   UpdateKioskDefaultMessageVisibility();
 }
 
-void LockContentsView::SetManagementDisclosureClient(
-    ManagementDisclosureClient* client) {
-  management_disclosure_client_ = client;
-}
-
 void LockContentsView::Layout(PassKey) {
   LayoutSuperclass<View>(this);
   LayoutTopHeader();
@@ -702,7 +716,7 @@
     if (old_state) {
       new_users.push_back(std::move(*old_state));
     } else {
-      new_users.push_back(UserState(user));
+      new_users.emplace_back(user);
     }
   }
 
@@ -2394,7 +2408,14 @@
   if (bottom_status_indicator_state_ != BottomIndicatorState::kManagedDevice) {
     return;
   }
-  management_bubble_->Show();
+
+  if (base::FeatureList::IsEnabled(
+          ash::features::kImprovedManagementDisclosure)) {
+    ShowManagementDisclosureDialog();
+  } else {
+    // Fallback to original bubble if management_disclosure not enabled.
+    management_bubble_->Show();
+  }
 }
 
 void LockContentsView::OnBackToSigninButtonTapped() {
@@ -2440,7 +2461,7 @@
       is_primary ? primary_big_view_.get() : opt_secondary_big_view_.get();
   AccountId user = to_update->GetCurrentUser().basic_user_info.account_id;
   data_dispatcher_->SetPinEnabledForUser(user, true,
-                                         /*avaiable_at=*/ std::nullopt);
+                                         /*available_at=*/std::nullopt);
   HideAuthErrorMessage();
 }
 
diff --git a/ash/login/ui/lock_contents_view.h b/ash/login/ui/lock_contents_view.h
index 87cc6fb..27ff87170 100644
--- a/ash/login/ui/lock_contents_view.h
+++ b/ash/login/ui/lock_contents_view.h
@@ -20,6 +20,7 @@
 #include "ash/login/ui/login_display_style.h"
 #include "ash/login/ui/login_error_bubble.h"
 #include "ash/login/ui/management_bubble.h"
+#include "ash/login/ui/management_disclosure_dialog.h"
 #include "ash/login/ui/non_accessible_view.h"
 #include "ash/login/ui/user_state.h"
 #include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
@@ -128,6 +129,8 @@
   void ShowAdbEnabled();
   void ToggleSystemInfo();
   void ShowParentAccessDialog();
+  // Shows the current device privacy disclosures.
+  void ShowManagementDisclosureDialog();
   void SetHasKioskApp(bool has_kiosk_apps);
 
   // views::View:
@@ -227,9 +230,6 @@
   void OnWillChangeFocus(View* focused_before, View* focused_now) override;
   void OnDidChangeFocus(View* focused_before, View* focused_now) override;
 
-  // Called by LockScreen.
-  void SetManagementDisclosureClient(ManagementDisclosureClient* client);
-
  private:
   using DisplayLayoutAction = base::RepeatingCallback<void(bool landscape)>;
 
@@ -474,6 +474,10 @@
   // Bubble for displaying warning banner message.
   raw_ptr<LoginErrorBubble> warning_banner_bubble_;
 
+  // The current ManagementDisclosureDialog, if one exists.
+  // Shows the list of device privacy disclosures.
+  base::WeakPtr<ManagementDisclosureDialog> management_disclosure_dialog_;
+
   // View that is shown on login timeout with camera usage.
   raw_ptr<LoginCameraTimeoutView, AcrossTasksDanglingUntriaged>
       login_camera_timeout_view_ = nullptr;
diff --git a/ash/login/ui/lock_contents_view_test_api.cc b/ash/login/ui/lock_contents_view_test_api.cc
index 1dc3dce9..f7741c0 100644
--- a/ash/login/ui/lock_contents_view_test_api.cc
+++ b/ash/login/ui/lock_contents_view_test_api.cc
@@ -113,6 +113,11 @@
   return view_->login_camera_timeout_view_;
 }
 
+base::WeakPtr<ManagementDisclosureDialog>
+LockContentsViewTestApi::management_disclosure_dialog() const {
+  return view_->management_disclosure_dialog_;
+}
+
 LoginBigUserView* LockContentsViewTestApi::FindBigUser(
     const AccountId& account_id) {
   LoginBigUserView* big_view =
diff --git a/ash/login/ui/lock_contents_view_test_api.h b/ash/login/ui/lock_contents_view_test_api.h
index f9de3d3..044928e 100644
--- a/ash/login/ui/lock_contents_view_test_api.h
+++ b/ash/login/ui/lock_contents_view_test_api.h
@@ -49,6 +49,8 @@
   views::View* main_view() const;
   const std::vector<UserState>& users() const;
   LoginCameraTimeoutView* login_camera_timeout_view() const;
+  base::WeakPtr<ManagementDisclosureDialog> management_disclosure_dialog()
+      const;
 
   // Finds and focuses (if needed) Big User View view specified by
   // |account_id|. Returns nullptr if the user not found.
diff --git a/ash/login/ui/lock_screen.cc b/ash/login/ui/lock_screen.cc
index 13f8796..c9eff3e3 100644
--- a/ash/login/ui/lock_screen.cc
+++ b/ash/login/ui/lock_screen.cc
@@ -197,6 +197,10 @@
   contents_view_->ShowParentAccessDialog();
 }
 
+void LockScreen::ShowManagementDisclosureDialog() {
+  contents_view_->ShowManagementDisclosureDialog();
+}
+
 void LockScreen::SetHasKioskApp(bool has_kiosk_apps) {
   contents_view_->SetHasKioskApp(has_kiosk_apps);
 }
diff --git a/ash/login/ui/lock_screen.h b/ash/login/ui/lock_screen.h
index 114496d..9a8cfc95 100644
--- a/ash/login/ui/lock_screen.h
+++ b/ash/login/ui/lock_screen.h
@@ -73,6 +73,8 @@
   void FocusNextUser();
   void FocusPreviousUser();
   void ShowParentAccessDialog();
+  // Shows the current device privacy disclosures.
+  void ShowManagementDisclosureDialog();
   void SetHasKioskApp(bool has_kiosk_apps);
 
   // TrayActionObserver:
diff --git a/ash/login/ui/management_disclosure_dialog.cc b/ash/login/ui/management_disclosure_dialog.cc
new file mode 100644
index 0000000..d59c627
--- /dev/null
+++ b/ash/login/ui/management_disclosure_dialog.cc
@@ -0,0 +1,247 @@
+// 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/login/ui/management_disclosure_dialog.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "ash/controls/rounded_scroll_bar.h"
+#include "ash/public/cpp/shell_window_ids.h"
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/session/session_controller_impl.h"
+#include "ash/shell.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
+#include "ash/style/ash_color_provider.h"
+#include "ash/system/model/enterprise_domain_model.h"
+#include "ash/system/model/system_tray_model.h"
+#include "base/memory/raw_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chromeos/constants/chromeos_features.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
+#include "chromeos/ui/vector_icons/vector_icons.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/vector_icons/vector_icons.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/models/image_model.h"
+#include "ui/chromeos/devicetype_utils.h"
+#include "ui/color/color_provider.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/text_constants.h"
+#include "ui/views/accessibility/view_accessibility.h"
+#include "ui/views/controls/bulleted_label_list/bulleted_label_list_view.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/scroll_view.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/box_layout_view.h"
+#include "ui/views/layout/layout_provider.h"
+#include "ui/views/layout/layout_types.h"
+#include "ui/views/view_class_properties.h"
+
+namespace ash {
+
+namespace {
+
+// Disclosure Pane.
+constexpr int kPaddingDp = 32;
+constexpr int kWidth = 600;
+constexpr int kHeight = 640;
+
+// Contents.
+constexpr int kTitleAndInfoPaddingDp = 20;
+constexpr int kLabelHeightDp = 16;
+
+// ManagedWarningView Title.
+constexpr char kManagedWarningClassName[] = "ManagedWarning";
+constexpr int kSpacingBetweenEnterpriseIconAndLabelDp = 20;
+constexpr int kEnterpriseIconSizeDp = 32;
+constexpr int kManagedWarningViewSize =
+    kSpacingBetweenEnterpriseIconAndLabelDp + kEnterpriseIconSizeDp +
+    kLabelHeightDp + (2 * kPaddingDp);
+
+std::unique_ptr<views::Label> CreateLabel(const std::u16string& text,
+                                          views::style::TextStyle style) {
+  auto label = std::make_unique<views::Label>(text);
+  label->SetSubpixelRenderingEnabled(false);
+  label->SetAutoColorReadabilityEnabled(false);
+  label->SetTextStyle(style);
+  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  label->SetEnabledColorId(
+      chromeos::features::IsJellyrollEnabled()
+          ? static_cast<ui::ColorId>(cros_tokens::kCrosSysOnSurface)
+          : kColorAshTextColorPrimary);
+  label->SetMultiLine(true);
+  return label;
+}
+
+views::Builder<views::ImageView> CreateEnterpriseIcon() {
+  return views::Builder<views::ImageView>()
+      .SetImage(ui::ImageModel::FromVectorIcon(
+          chromeos::kEnterpriseIcon, ui::kColorIcon, kEnterpriseIconSizeDp))
+      .SetHorizontalAlignment(views::ImageViewBase::Alignment::kLeading);
+}
+
+// Calculates the area of the views above the scrollable area and adds padding
+// for the close button.
+int GetDisclosureViewHeight() {
+  return kManagedWarningViewSize + (3 * kLabelHeightDp) + (4 * kPaddingDp);
+}
+
+}  // namespace
+
+// Container for the device monitoring warning. Composed of an optional warning
+// icon on the left and a label to the right.
+class ManagedWarningView : public NonAccessibleView {
+  METADATA_HEADER(ManagedWarningView, NonAccessibleView)
+
+ public:
+  ManagedWarningView() : NonAccessibleView(kManagedWarningClassName) {
+    const std::u16string label_text = l10n_util::GetStringFUTF16(
+        IDS_MANAGEMENT_SUBTITLE_MANAGED_BY, ui::GetChromeOSDeviceName(),
+        base::UTF8ToUTF16(Shell::Get()
+                              ->system_tray_model()
+                              ->enterprise_domain()
+                              ->enterprise_domain_manager()));
+    auto enterprise_image = CreateEnterpriseIcon();
+    views::Builder<views::View>(this)
+        .SetLayoutManager(std::make_unique<views::BoxLayout>(
+            views::LayoutOrientation::kVertical, gfx::Insets(),
+            kSpacingBetweenEnterpriseIconAndLabelDp))
+        .SetProperty(views::kMarginsKey,
+                     gfx::Insets::TLBR(kPaddingDp, kPaddingDp, 0, kPaddingDp))
+        .AddChildren(
+            enterprise_image,
+            views::Builder<views::Label>(
+                CreateLabel(label_text, views::style::STYLE_HEADLINE_5))
+                .SetMultiLine(true))
+        .BuildChildren();
+  }
+
+  ManagedWarningView(const ManagedWarningView&) = delete;
+  ManagedWarningView& operator=(const ManagedWarningView&) = delete;
+
+  ~ManagedWarningView() override = default;
+};
+
+BEGIN_METADATA(ManagedWarningView)
+END_METADATA
+
+ManagementDisclosureDialog::ManagementDisclosureDialog(
+    const std::vector<std::u16string> disclosures,
+    base::OnceClosure on_dismissed_callback) {
+  SetModalType(ui::mojom::ModalType::kSystem);
+
+  SetButtonLabel(ui::mojom::DialogButton::kOk,
+                 l10n_util::GetStringUTF16(IDS_CLOSE));
+  // Only have the close button.
+  SetButtons(static_cast<int>(ui::mojom::DialogButton::kOk));
+  SetShowCloseButton(false);
+  auto pair_cb = base::SplitOnceCallback(std::move(on_dismissed_callback));
+  SetAcceptCallback(std::move(pair_cb.first));
+  SetCancelCallback(std::move(pair_cb.second));
+  SetShowTitle(false);
+
+  SetPreferredSize(GetPreferredSize());
+
+  SetLayoutManager(std::make_unique<views::BoxLayout>())
+      ->SetOrientation(views::BoxLayout::Orientation::kVertical);
+  // layout_->SetOrientation(views::BoxLayout::Orientation::kVertical);
+
+  // Set up view for the header that contains management icon and who manages
+  // the device.
+  AddChildView(std::make_unique<ManagedWarningView>());
+
+  // Set up disclosure pane that will contain informational text as well as
+  // disclosures.
+  auto* disclosure_view = AddChildView(
+      views::Builder<views::BoxLayoutView>()
+          .SetOrientation(views::BoxLayout::Orientation::kVertical)
+          .SetProperty(views::kMarginsKey,
+                       gfx::Insets::TLBR(kPaddingDp / 2, kPaddingDp,
+                                         2 * kPaddingDp, kPaddingDp))
+          .Build());
+
+  views::LayoutProvider* provider = views::LayoutProvider::Get();
+  // Create information labels.
+  views::Builder<views::View>(disclosure_view)
+      .AddChildren(
+          views::Builder<views::Label>(
+              CreateLabel(l10n_util::GetStringUTF16(
+                              IDS_MANAGEMENT_OPEN_CHROME_MANAGEMENT),
+                          views::style::STYLE_BODY_5))
+              .SetProperty(views::kMarginsKey,
+                           gfx::Insets().set_top(kTitleAndInfoPaddingDp)),
+          views::Builder<views::Label>(
+              CreateLabel(l10n_util::GetStringUTF16(
+                              IDS_MANAGEMENT_PROXY_SERVER_PRIVACY_DISCLOSURE),
+                          views::style::STYLE_BODY_5))
+              .SetProperty(views::kMarginsKey,
+                           gfx::Insets().set_bottom(provider->GetDistanceMetric(
+                               views::DistanceMetric::
+                                   DISTANCE_UNRELATED_CONTROL_VERTICAL))),
+          views::Builder<views::Label>(CreateLabel(
+              l10n_util::GetStringUTF16(IDS_MANAGEMENT_DEVICE_CONFIGURATION),
+              views::style::STYLE_BODY_5)))
+      .BuildChildren();
+
+  // Set up scroll bar.
+  auto* scroll_view = disclosure_view->AddChildView(
+      views::Builder<views::ScrollView>()
+          .SetHorizontalScrollBarMode(
+              views::ScrollView::ScrollBarMode::kDisabled)
+          .SetDrawOverflowIndicator(false)
+          // Fill the remaining space with the list of disclosures with padding
+          // on the bottom for close button.
+          .ClipHeightTo(0,
+                        GetPreferredSize().height() - GetDisclosureViewHeight())
+          .SetBackgroundColor(std::nullopt)
+          .SetAllowKeyboardScrolling(true)
+          .Build());
+
+  // Set up vertical scroll bar.
+  auto vertical_scroll = std::make_unique<RoundedScrollBar>(
+      views::ScrollBar::Orientation::kVertical);
+  vertical_scroll->SetSnapBackOnDragOutside(false);
+  scroll_view->SetVerticalScrollBar(std::move(vertical_scroll));
+  scroll_view->SetContents(std::make_unique<views::BulletedLabelListView>(
+      disclosures, views::style::STYLE_BODY_5));
+
+  // Parent the dialog widget to the LockSystemModalContainer to ensure that it
+  // will get displayed on respective lock/signin or OOBE screen.
+  SessionControllerImpl* session_controller =
+      Shell::Get()->session_controller();
+  int container_id = kShellWindowId_SystemModalContainer;
+  if (session_controller->IsUserSessionBlocked() ||
+      session_controller->GetSessionState() ==
+          session_manager::SessionState::OOBE) {
+    container_id = kShellWindowId_LockSystemModalContainer;
+  }
+  Shell::GetContainer(Shell::GetPrimaryRootWindow(), container_id);
+
+  views::Widget* widget = CreateDialogWidget(
+      this, nullptr,
+      Shell::GetContainer(Shell::GetPrimaryRootWindow(), container_id));
+  widget->Show();
+}
+
+ManagementDisclosureDialog::~ManagementDisclosureDialog() = default;
+
+gfx::Size ManagementDisclosureDialog::GetPreferredSize() {
+  return gfx::Size{kWidth, kHeight};
+}
+
+base::WeakPtr<ManagementDisclosureDialog>
+ManagementDisclosureDialog::GetWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
+BEGIN_METADATA(ManagementDisclosureDialog)
+END_METADATA
+
+}  // namespace ash
diff --git a/ash/login/ui/management_disclosure_dialog.h b/ash/login/ui/management_disclosure_dialog.h
new file mode 100644
index 0000000..c97bc69
--- /dev/null
+++ b/ash/login/ui/management_disclosure_dialog.h
@@ -0,0 +1,50 @@
+// 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_LOGIN_UI_MANAGEMENT_DISCLOSURE_DIALOG_H_
+#define ASH_LOGIN_UI_MANAGEMENT_DISCLOSURE_DIALOG_H_
+
+#include <memory>
+
+#include "ash/ash_export.h"
+#include "ash/login/ui/non_accessible_view.h"
+#include "base/memory/raw_ptr.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/controls/bulleted_label_list/bulleted_label_list_view.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/styled_label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/view.h"
+#include "ui/views/window/dialog_delegate.h"
+
+namespace ash {
+
+// Implements a window that displays the current device disclosures.
+class ASH_EXPORT ManagementDisclosureDialog : public views::DialogDelegateView {
+  METADATA_HEADER(ManagementDisclosureDialog, views::DialogDelegateView)
+
+ public:
+  using OnManagementDisclosureDismissed = base::RepeatingClosure;
+  explicit ManagementDisclosureDialog(
+      const std::vector<std::u16string> disclosures,
+      base::OnceClosure on_dismissed_callback);
+
+  ManagementDisclosureDialog(const ManagementDisclosureDialog&) = delete;
+  ManagementDisclosureDialog& operator=(const ManagementDisclosureDialog&) =
+      delete;
+
+  ~ManagementDisclosureDialog() override;
+
+  static gfx::Size GetPreferredSize();
+
+  base::WeakPtr<ManagementDisclosureDialog> GetWeakPtr();
+
+ private:
+  base::WeakPtrFactory<ManagementDisclosureDialog> weak_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // ASH_LOGIN_UI_MANAGEMENT_DISCLOSURE_DIALOG_H_
diff --git a/ash/login/ui/management_disclosure_field_trial.cc b/ash/login/ui/management_disclosure_field_trial.cc
index 9e26d7c..9ab405837 100644
--- a/ash/login/ui/management_disclosure_field_trial.cc
+++ b/ash/login/ui/management_disclosure_field_trial.cc
@@ -19,7 +19,7 @@
 namespace {
 
 // The field trial name.
-const char kTrialName[] = "ManagementDisclosureView";
+const char kTrialName[] = "ManagementDisclosure";
 
 // Group names for the trial.
 const char kEnabledGroup[] = "Enabled";
diff --git a/ash/login/ui/management_disclosure_view.cc b/ash/login/ui/management_disclosure_view.cc
deleted file mode 100644
index 69315e256..0000000
--- a/ash/login/ui/management_disclosure_view.cc
+++ /dev/null
@@ -1,474 +0,0 @@
-// 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/login/ui/management_disclosure_view.h"
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "ash/controls/rounded_scroll_bar.h"
-#include "ash/login/ui/views_utils.h"
-#include "ash/public/cpp/login_types.h"
-#include "ash/public/cpp/shelf_config.h"
-#include "ash/resources/vector_icons/vector_icons.h"
-#include "ash/shell.h"
-#include "ash/strings/grit/ash_strings.h"
-#include "ash/style/ash_color_id.h"
-#include "ash/style/ash_color_provider.h"
-#include "ash/style/pill_button.h"
-#include "ash/style/system_shadow.h"
-#include "base/functional/bind.h"
-#include "base/functional/callback.h"
-#include "base/functional/callback_helpers.h"
-#include "base/memory/raw_ptr.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chromeos/constants/chromeos_features.h"
-#include "chromeos/strings/grit/chromeos_strings.h"
-#include "chromeos/ui/vector_icons/vector_icons.h"
-#include "components/strings/grit/components_strings.h"
-#include "components/vector_icons/vector_icons.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/base/models/image_model.h"
-#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
-#include "ui/compositor/layer.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/gfx/text_constants.h"
-#include "ui/views/accessibility/view_accessibility.h"
-#include "ui/views/background.h"
-#include "ui/views/border.h"
-#include "ui/views/controls/image_view.h"
-#include "ui/views/controls/label.h"
-#include "ui/views/controls/scroll_view.h"
-#include "ui/views/highlight_border.h"
-#include "ui/views/layout/box_layout.h"
-#include "ui/views/layout/box_layout_view.h"
-#include "ui/views/layout/flex_layout.h"
-#include "ui/views/layout/layout_provider.h"
-#include "ui/views/layout/layout_types.h"
-#include "ui/views/mouse_constants.h"
-#include "ui/views/style/typography.h"
-#include "ui/views/style/typography_provider.h"
-#include "ui/views/view_class_properties.h"
-
-namespace ash {
-
-namespace {
-
-constexpr const char kManagementDisclosureViewClassName[] =
-    "ManagementDisclosureView";
-
-// Landscape Pane.
-constexpr int kTopPaddingDp = 100;
-constexpr int kShelfPaddingDp = 100;
-constexpr int kDefaultShelfHeightDp = 48;
-constexpr int kPaddingDp = 32;
-
-// ManagedWarningView Title.
-constexpr char kManagedWarningClassName[] = "ManagedWarning";
-constexpr int kSpacingBetweenEnterpriseIconAndLabelDp = 20;
-constexpr int kEnterpriseIconSizeDp = 32;
-
-// Contents.
-constexpr int kTitleAndInfoPaddingDp = 20;
-
-// Bullet.
-constexpr int kBulletLabelPaddingDp = 3;
-constexpr int kBulletRadiusDp = 3;
-constexpr int kBulletContainerSizeDp = 30;
-
-std::unique_ptr<views::Label> CreateLabel(const std::u16string& text,
-                                          views::style::TextStyle style) {
-  auto label = std::make_unique<views::Label>(text);
-  label->SetSubpixelRenderingEnabled(false);
-  label->SetAutoColorReadabilityEnabled(false);
-  label->SetTextStyle(style);
-  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  label->SetEnabledColorId(
-      chromeos::features::IsJellyrollEnabled()
-          ? static_cast<ui::ColorId>(cros_tokens::kCrosSysOnSurface)
-          : kColorAshTextColorPrimary);
-  label->SetMultiLine(true);
-  return label;
-}
-
-class ManagementDisclosureEventHandler : public ui::EventHandler {
- public:
-  explicit ManagementDisclosureEventHandler(ManagementDisclosureView* view)
-      : view_(view) {
-    // Used so that when a user clicks area outside disclosure window it closes.
-    Shell::Get()->AddPreTargetHandler(this);
-  }
-
-  ManagementDisclosureEventHandler(const ManagementDisclosureEventHandler&) =
-      delete;
-  ManagementDisclosureEventHandler& operator=(
-      const ManagementDisclosureEventHandler&) = delete;
-
-  ~ManagementDisclosureEventHandler() override {
-    Shell::Get()->RemovePreTargetHandler(this);
-  }
-
- private:
-  // ui::EventHandler:
-  void OnMouseEvent(ui::MouseEvent* event) override {
-    if (event->type() == ui::EventType::kMousePressed) {
-      view_->ProcessPressedEvent(event->AsLocatedEvent());
-    }
-  }
-  void OnGestureEvent(ui::GestureEvent* event) override {
-    if ((event->type() == ui::EventType::kGestureTap ||
-         event->type() == ui::EventType::kGestureTapDown)) {
-      view_->ProcessPressedEvent(event->AsLocatedEvent());
-    }
-  }
-  void OnKeyEvent(ui::KeyEvent* event) override { view_->OnKeyEvent(event); }
-
-  raw_ptr<ManagementDisclosureView> view_;
-};
-
-class BulletView : public views::View {
-  METADATA_HEADER(BulletView, views::View)
-
- public:
-  explicit BulletView(SkColor color, int radius)
-      : color_(color), radius_(radius) {}
-  BulletView(const BulletView&) = delete;
-  BulletView& operator=(const BulletView&) = delete;
-  ~BulletView() override = default;
-
-  // views::View:
-  void OnPaint(gfx::Canvas* canvas) override {
-    View::OnPaint(canvas);
-
-    SkPath path;
-    path.addCircle(GetLocalBounds().CenterPoint().x(),
-                   GetLocalBounds().CenterPoint().y(), radius_);
-    cc::PaintFlags flags;
-    flags.setStyle(cc::PaintFlags::kFill_Style);
-    flags.setColor(color_);
-    flags.setAntiAlias(true);
-
-    canvas->DrawPath(path, flags);
-  }
-
- private:
-  SkColor color_;
-  int radius_;
-};
-
-BEGIN_METADATA(BulletView)
-END_METADATA
-
-}  // namespace
-
-// Container for the device monitoring warning. Composed of an optional warning
-// icon on the left and a label to the right.
-class ManagedWarningView : public NonAccessibleView {
-  METADATA_HEADER(ManagedWarningView, NonAccessibleView)
-
- public:
-  ManagedWarningView() : NonAccessibleView(kManagedWarningClassName) {
-    // TODO(b/330527825): Replace with string.
-    const std::u16string label_text = u"Your chromebook is managed by ";
-    // base::UTF8ToUTF16(device_manager_.value());
-
-    views::LayoutProvider* provider = views::LayoutProvider::Get();
-
-    views::Builder<views::View>(this)
-        .SetLayoutManager(std::make_unique<views::BoxLayout>(
-            views::LayoutOrientation::kVertical, gfx::Insets(),
-            kSpacingBetweenEnterpriseIconAndLabelDp))
-        .AddChildren(
-            views::Builder<views::ImageView>().CopyAddressTo(&image_).SetImage(
-                ui::ImageModel::FromVectorIcon(chromeos::kEnterpriseIcon,
-                                               kEnterpriseIconSizeDp)),
-            views::Builder<views::View>()
-                .CopyAddressTo(&placeholder_)
-                .SetVisible(true)
-                .SetPreferredSize(gfx::Size(0, kEnterpriseIconSizeDp)),
-            views::Builder<views::Label>(
-                CreateLabel(label_text, views::style::STYLE_HEADLINE_5))
-                .SetMultiLine(true)
-                .CopyAddressTo(&label_)
-                .SetLineHeight(provider->GetDistanceMetric(
-                    views::DistanceMetric::DISTANCE_UNRELATED_CONTROL_VERTICAL))
-                .SetProperty(
-                    views::kMarginsKey,
-                    gfx::Insets(provider->GetDistanceMetric(
-                        views::DistanceMetric::
-                            DISTANCE_DIALOG_CONTENT_MARGIN_TOP_CONTROL))))
-        .BuildChildren();
-  }
-
-  ManagedWarningView(const ManagedWarningView&) = delete;
-  ManagedWarningView& operator=(const ManagedWarningView&) = delete;
-
-  ~ManagedWarningView() override = default;
-
- private:
-  std::optional<std::string> device_manager_;
-  raw_ptr<views::ImageView> image_;
-  raw_ptr<views::Label> label_;
-  raw_ptr<views::View> placeholder_;
-};
-
-BEGIN_METADATA(ManagedWarningView)
-END_METADATA
-
-ManagementDisclosureView::ManagementDisclosureView(
-    const OnManagementDisclosureDismissed& on_dismissed)
-    : NonAccessibleView(kManagementDisclosureViewClassName),
-      on_dismissed_(on_dismissed),
-      event_handler_(std::make_unique<ManagementDisclosureEventHandler>(this)) {
-  views::LayoutProvider* provider = views::LayoutProvider::Get();
-
-  if (chromeos::features::IsJellyrollEnabled()) {
-    SetBackground(views::CreateThemedRoundedRectBackground(
-        cros_tokens::kCrosSysSystemBaseElevated,
-        provider->GetCornerRadiusMetric(
-            views::ShapeContextTokens::kSidePanelContentRadius)));
-    SetBorder(std::make_unique<views::HighlightBorder>(
-        provider->GetCornerRadiusMetric(
-            views::ShapeContextTokens::kSidePanelContentRadius),
-        views::HighlightBorder::Type::kHighlightBorderOnShadow));
-    shadow_ = SystemShadow::CreateShadowOnNinePatchLayerForView(
-        this, SystemShadow::Type::kElevation12);
-    shadow_->SetRoundedCornerRadius(provider->GetCornerRadiusMetric(
-        views::ShapeContextTokens::kSidePanelContentRadius));
-  } else {
-    SetBackground(views::CreateThemedRoundedRectBackground(
-        kColorAshShieldAndBase80,
-        provider->GetCornerRadiusMetric(
-            views::ShapeContextTokens::kBadgeRadius)));
-  }
-
-  SetPreferredSize(GetPreferredSizeLandscape());
-
-  layout_ = SetLayoutManager(std::make_unique<views::BoxLayout>());
-  layout_->SetOrientation(views::BoxLayout::Orientation::kVertical);
-
-  // Set up view for the header that contains management icon and who manages
-  // the device.
-  managed_warning_view_ = AddChildView(std::make_unique<ManagedWarningView>());
-
-  // Set up disclosure pane that will contain informational text as well as
-  // disclosures.
-  disclosure_view_ =
-      AddChildView(views::Builder<views::BoxLayoutView>()
-                       .SetOrientation(views::BoxLayout::Orientation::kVertical)
-                       .Build());
-
-  // Create information labels.
-  views::Builder<views::View>(disclosure_view_.get())
-      .AddChildren(
-          views::Builder<views::Label>(
-              CreateLabel(l10n_util::GetStringUTF16(
-                              IDS_MANAGEMENT_OPEN_CHROME_MANAGEMENT),
-                          views::style::STYLE_BODY_5))
-              .CopyAddressTo(&admin_description_label_)
-              .SetProperty(
-                  views::kMarginsKey,
-                  gfx::Insets().set_top(kTitleAndInfoPaddingDp).set_bottom(0)),
-          views::Builder<views::Label>(
-              CreateLabel(l10n_util::GetStringUTF16(
-                              IDS_MANAGEMENT_PROXY_SERVER_PRIVACY_DISCLOSURE),
-                          views::style::STYLE_BODY_5))
-              .CopyAddressTo(&additional_information_label_)
-              .SetProperty(views::kMarginsKey,
-                           gfx::Insets().set_top(0).set_bottom(
-                               provider->GetDistanceMetric(
-                                   views::DistanceMetric::
-                                       DISTANCE_UNRELATED_CONTROL_VERTICAL))),
-          views::Builder<views::Label>(
-
-              CreateLabel(l10n_util::GetStringUTF16(
-                              IDS_MANAGEMENT_DEVICE_CONFIGURATION),
-                          views::style::STYLE_BODY_5))
-              .CopyAddressTo(&may_be_able_to_view_title_))
-      .BuildChildren();
-
-  // Set up scroll bar.
-  scroll_view_ = disclosure_view_->AddChildView(
-      views::Builder<views::ScrollView>()
-          .SetHorizontalScrollBarMode(
-              views::ScrollView::ScrollBarMode::kDisabled)
-          .SetDrawOverflowIndicator(false)
-          .ClipHeightTo(0, std::numeric_limits<int>::max())
-          .SetBackgroundColor(std::nullopt)
-          .SetAllowKeyboardScrolling(true)
-          .Build());
-
-  // Set up vertical scroll bar.
-  auto vertical_scroll = std::make_unique<RoundedScrollBar>(
-      views::ScrollBar::Orientation::kVertical);
-  vertical_scroll->SetSnapBackOnDragOutside(false);
-  scroll_view_->SetVerticalScrollBar(std::move(vertical_scroll));
-
-  // Set up scroll contents.
-  auto scroll_contents = std::make_unique<views::View>();
-  auto* layout =
-      scroll_contents->SetLayoutManager(std::make_unique<views::BoxLayout>(
-          views::BoxLayout::Orientation::kVertical));
-  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
-
-  // Create bulleted list.
-  auto add_bulleted_label = [&](const std::u16string& text) {
-    auto* container = new views::View();
-    auto layout = std::make_unique<views::FlexLayout>();
-    // Align the bullet to the top line of multi-line labels.
-    layout->SetCrossAxisAlignment(views::LayoutAlignment::kStart);
-    container->SetLayoutManager(std::move(layout));
-    auto label =
-        views::Builder<views::Label>(
-            CreateLabel(text, views::style::STYLE_BODY_5))
-            .SetMaximumWidth(disclosure_view_->width() -
-                             kBulletContainerSizeDp - kPaddingDp)
-            .SetHorizontalAlignment(gfx::ALIGN_LEFT)
-            .SetProperty(
-                views::kMarginsKey,
-                gfx::Insets::TLBR(kBulletLabelPaddingDp, kBulletLabelPaddingDp,
-                                  kBulletLabelPaddingDp, kPaddingDp))
-            .Build();
-
-    BulletView* bullet_view =
-        new BulletView(kColorAshTextColorPrimary, kBulletRadiusDp);
-    bullet_view->SetPreferredSize(
-        gfx::Size(kBulletContainerSizeDp, kBulletContainerSizeDp));
-
-    container->AddChildView(bullet_view);
-    container->AddChildView(label.get());
-    scroll_contents->AddChildView(container);
-  };
-
-  // These are just placeholder disclosures.
-  // Add bulleted list of device disclosures.
-  add_bulleted_label(
-      l10n_util::GetStringUTF16(IDS_MANAGEMENT_REPORT_DEVICE_ACTIVITY_TIMES));
-  add_bulleted_label(
-      l10n_util::GetStringUTF16(IDS_MANAGEMENT_REPORT_DEVICE_NETWORK_DATA));
-  add_bulleted_label(
-      l10n_util::GetStringUTF16(IDS_MANAGEMENT_REPORT_APP_INFO_AND_ACTIVITY));
-  //add_bulleted_label(
-  //    l10n_util::GetStringUTF16(IDS_MANAGEMENT_LOG_UPLOAD_ENABLED_NO_LINK));
-  //add_bulleted_label(
-     // l10n_util::GetStringUTF16(IDS_MANAGEMENT_LEGACY_TECH_REPORT_NO_LINK));
-
-  scroll_view_->SetContents(std::move(scroll_contents));
-
-  // Close button.
-  close_button_ = AddChildView(
-      views::Builder<PillButton>()
-          .SetCallback(base::BindRepeating(&ManagementDisclosureView::Hide,
-                                           base::Unretained(this)))
-          .SetText(l10n_util::GetStringUTF16(IDS_CLOSE))
-          .SetProperty(views::kViewIgnoredByLayoutKey, true)
-          .Build());
-  if (chromeos::features::IsJellyrollEnabled()) {
-    close_button_->SetBackgroundColorId(
-        cros_tokens::kCrosSysSystemPrimaryContainer);
-  }
-}
-
-ManagementDisclosureView::~ManagementDisclosureView() = default;
-
-void ManagementDisclosureView::ProcessPressedEvent(
-    const ui::LocatedEvent* event) {
-  if (!GetVisible()) {
-    return;
-  }
-
-  if (GetBoundsInScreen().Contains(event->root_location())) {
-    return;
-  }
-
-  Hide();
-}
-
-void ManagementDisclosureView::Hide() {
-  shadow_.reset();
-  SetVisible(false);
-  on_dismissed_.Run();
-}
-
-gfx::Size ManagementDisclosureView::GetPreferredSizeLandscape() {
-  gfx::Rect bounds = display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
-  bounds.Inset(gfx::Insets::TLBR(kTopPaddingDp, bounds.width() / 4,
-                                 ShelfConfig::Get()
-                                     ? ShelfConfig::Get()->shelf_size()
-                                     : kDefaultShelfHeightDp + kShelfPaddingDp,
-                                 bounds.width() / 4));
-  return bounds.size();
-}
-gfx::Size ManagementDisclosureView::GetPreferredSizePortrait() {
-  gfx::Rect bounds = display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
-
-  // Want to keep the width the same between landscape and portrait.
-  auto landscape_width = bounds.height() / 2;
-  int width_insets;
-  if (landscape_width < bounds.width() - (kPaddingDp * 2)) {
-    width_insets = bounds.height() / 4;
-  } else {
-    width_insets = kPaddingDp;
-  }
-  bounds.Inset(gfx::Insets::TLBR(kTopPaddingDp, width_insets,
-                                 ShelfConfig::Get()
-                                     ? ShelfConfig::Get()->shelf_size()
-                                     : kDefaultShelfHeightDp + kShelfPaddingDp,
-                                 width_insets));
-  return bounds.size();
-}
-
-void ManagementDisclosureView::OnBoundsChanged(
-    const gfx::Rect& previous_bounds) {
-  if (bounds().width() >= bounds().height()) {
-    UseLandscapeLayout();
-  } else {
-    UsePortraitLayout();
-  }
-}
-
-void ManagementDisclosureView::Layout(PassKey) {
-  LayoutSuperclass<View>(this);
-
-  close_button_->SizeToPreferredSize();
-  const int submit_button_x =
-      size().width() - kPaddingDp - close_button_->size().width();
-  const int submit_button_y =
-      size().height() - kPaddingDp - close_button_->size().height();
-  close_button_->SetPosition(gfx::Point{submit_button_x, submit_button_y});
-}
-
-void ManagementDisclosureView::OnKeyEvent(ui::KeyEvent* event) {
-  if (!GetVisible() || event->type() != ui::EventType::kKeyPressed) {
-    return;
-  }
-
-  if (event->key_code() == ui::KeyboardCode::VKEY_ESCAPE) {
-    Hide();
-  }
-}
-
-void ManagementDisclosureView::UseLandscapeLayout() {
-  disclosure_view_->SetPreferredSize(GetPreferredSizeLandscape());
-  disclosure_view_->SetProperty(
-      views::kMarginsKey,
-      gfx::Insets::TLBR(kPaddingDp / 2, kPaddingDp,
-                        close_button_->height() + kPaddingDp, kPaddingDp));
-}
-
-void ManagementDisclosureView::UsePortraitLayout() {
-  disclosure_view_->SetPreferredSize(GetPreferredSizePortrait());
-  disclosure_view_->SetProperty(
-      views::kMarginsKey,
-      gfx::Insets::TLBR(kPaddingDp / 2, kPaddingDp,
-                        close_button_->height() + kPaddingDp, kPaddingDp));
-}
-
-BEGIN_METADATA(ManagementDisclosureView)
-END_METADATA
-
-}  // namespace ash
diff --git a/ash/login/ui/management_disclosure_view.h b/ash/login/ui/management_disclosure_view.h
deleted file mode 100644
index 8eef8f49..0000000
--- a/ash/login/ui/management_disclosure_view.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// 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_LOGIN_UI_MANAGEMENT_DISCLOSURE_VIEW_H_
-#define ASH_LOGIN_UI_MANAGEMENT_DISCLOSURE_VIEW_H_
-
-#include <memory>
-
-#include "ash/ash_export.h"
-#include "ash/login/ui/non_accessible_view.h"
-#include "ash/login/ui/public_account_menu_view.h"
-#include "ash/style/pill_button.h"
-#include "ash/style/system_shadow.h"
-#include "base/memory/raw_ptr.h"
-#include "ui/base/metadata/metadata_header_macros.h"
-#include "ui/events/event_handler.h"
-#include "ui/views/controls/image_view.h"
-#include "ui/views/controls/label.h"
-#include "ui/views/controls/styled_label.h"
-#include "ui/views/layout/box_layout.h"
-#include "ui/views/view.h"
-
-namespace ash {
-
-class ManagedWarningView;
-
-// Implements an expanded view for the public account user to select language
-// and keyboard options.
-class ASH_EXPORT ManagementDisclosureView : public NonAccessibleView {
-  METADATA_HEADER(ManagementDisclosureView, NonAccessibleView)
-
- public:
-  using OnManagementDisclosureDismissed = base::RepeatingClosure;
-  explicit ManagementDisclosureView(
-      const OnManagementDisclosureDismissed& on_dismissed);
-
-  ManagementDisclosureView(const ManagementDisclosureView&) = delete;
-  ManagementDisclosureView& operator=(const ManagementDisclosureView&) = delete;
-
-  ~ManagementDisclosureView() override;
-
-  void ProcessPressedEvent(const ui::LocatedEvent* event);
-  void Hide();
-
-  static gfx::Size GetPreferredSizeLandscape();
-  static gfx::Size GetPreferredSizePortrait();
-
-  // views::View:
-  void Layout(PassKey) override;
-  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
-
-  // ui::EventHandler:
-  void OnKeyEvent(ui::KeyEvent* event) override;
-
- private:
-  void UseLandscapeLayout();
-  void UsePortraitLayout();
-
-  raw_ptr<views::BoxLayout> layout_ = nullptr;
-  raw_ptr<ManagedWarningView> managed_warning_view_ = nullptr;
-  raw_ptr<views::View> disclosure_view_ = nullptr;
-  raw_ptr<PillButton> close_button_ = nullptr;
-  raw_ptr<views::Label> admin_description_label_ = nullptr;
-  raw_ptr<views::Label> additional_information_label_ = nullptr;
-  raw_ptr<views::Label> may_be_able_to_view_title_ = nullptr;
-  raw_ptr<views::ScrollView> scroll_view_ = nullptr;
-
-  OnManagementDisclosureDismissed on_dismissed_;
-  std::unique_ptr<ui::EventHandler> event_handler_;
-  std::unique_ptr<SystemShadow> shadow_;
-
-  base::WeakPtrFactory<ManagementDisclosureView> weak_factory_{this};
-};
-
-}  // namespace ash
-
-#endif  // ASH_LOGIN_UI_MANAGEMENT_DISCLOSURE_VIEW_H_
diff --git a/ash/public/cpp/ambient/ambient_backend_controller.h b/ash/public/cpp/ambient/ambient_backend_controller.h
index dbe857e4..f3eec0b4 100644
--- a/ash/public/cpp/ambient/ambient_backend_controller.h
+++ b/ash/public/cpp/ambient/ambient_backend_controller.h
@@ -127,10 +127,7 @@
   // `weather_client_id` - the weather client ID that should be passed to the
   // weather request, use nullopt to use the default weather client ID (used
   // for ambient mode).
-  // `prefer_alpha_endpoint` - whether request should use alpha/dev endpoint of
-  // the server providing weather information.
   virtual void FetchWeather(std::optional<std::string> weather_client_id,
-                            bool prefer_alpha_endpoint,
                             FetchWeatherCallback callback) = 0;
 
   // Get stock photo urls to cache in advance in case Ambient mode is started
diff --git a/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc b/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc
index 56dbbae..d0fea22 100644
--- a/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc
+++ b/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc
@@ -163,7 +163,6 @@
 
 void FakeAmbientBackendControllerImpl::FetchWeather(
     std::optional<std::string> weather_client_id,
-    bool prefer_aplha_endpoint,
     FetchWeatherCallback callback) {
   ++fetch_weather_count_;
   weather_client_id_ = weather_client_id;
diff --git a/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h b/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h
index 87531298..06f73f6 100644
--- a/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h
+++ b/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h
@@ -41,7 +41,6 @@
       int num_albums,
       OnSettingsAndAlbumsFetchedCallback callback) override;
   void FetchWeather(std::optional<std::string> weather_client_id,
-                    bool prefer_aplha_endpoint,
                     FetchWeatherCallback callback) override;
   const std::array<const char*, 2>& GetBackupPhotoUrls() const override;
   std::array<const char*, 2> GetTimeOfDayVideoPreviewImageUrls(
diff --git a/ash/public/cpp/management_disclosure_client.h b/ash/public/cpp/management_disclosure_client.h
index 0279d0a0e..12310b2 100644
--- a/ash/public/cpp/management_disclosure_client.h
+++ b/ash/public/cpp/management_disclosure_client.h
@@ -5,6 +5,9 @@
 #ifndef ASH_PUBLIC_CPP_MANAGEMENT_DISCLOSURE_CLIENT_H_
 #define ASH_PUBLIC_CPP_MANAGEMENT_DISCLOSURE_CLIENT_H_
 
+#include <string>
+#include <vector>
+
 #include "ash/public/cpp/ash_public_export.h"
 
 namespace ash {
@@ -18,7 +21,11 @@
   ManagementDisclosureClient& operator=(const ManagementDisclosureClient&) =
       delete;
 
-  virtual void SetVisible(bool visible) = 0;
+  // Retrieves the list of device policy disclosures from the
+  // management_ui_handler (same place chrome://management is populated from so
+  // they should match). The device disclosures are than passed to
+  // management_disclosure_dialog so they can be shown on the login/lock screen.
+  virtual std::vector<std::u16string> GetDisclosures() = 0;
 
  protected:
   virtual ~ManagementDisclosureClient();
diff --git a/ash/system/accessibility/floating_menu_button.cc b/ash/system/accessibility/floating_menu_button.cc
index f418be0..d54c71a 100644
--- a/ash/system/accessibility/floating_menu_button.cc
+++ b/ash/system/accessibility/floating_menu_button.cc
@@ -146,13 +146,6 @@
   return gfx::Size(size_, size_);
 }
 
-void FloatingMenuButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  if (!GetEnabled()) {
-    return;
-  }
-  views::ImageButton::GetAccessibleNodeData(node_data);
-}
-
 void FloatingMenuButton::UpdateImage() {
   DCHECK(icon_);
   const ui::ColorId icon_color_id =
diff --git a/ash/system/accessibility/floating_menu_button.h b/ash/system/accessibility/floating_menu_button.h
index 3fc6b41..09a5659f 100644
--- a/ash/system/accessibility/floating_menu_button.h
+++ b/ash/system/accessibility/floating_menu_button.h
@@ -60,7 +60,6 @@
   void PaintButtonContents(gfx::Canvas* canvas) override;
   gfx::Size CalculatePreferredSize(
       const views::SizeBounds& available_size) const override;
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
  private:
   void UpdateImage();
diff --git a/ash/system/accessibility/switch_access/switch_access_menu_button.cc b/ash/system/accessibility/switch_access/switch_access_menu_button.cc
index 5b1f4a0..b225e18 100644
--- a/ash/system/accessibility/switch_access/switch_access_menu_button.cc
+++ b/ash/system/accessibility/switch_access/switch_access_menu_button.cc
@@ -85,10 +85,6 @@
   GetViewAccessibility().SetValue(action_name);
 }
 
-void SwitchAccessMenuButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  views::Button::GetAccessibleNodeData(node_data);
-}
-
 void SwitchAccessMenuButton::OnButtonPressed() {
   NotifyAccessibilityEvent(ax::mojom::Event::kClicked,
                            /*send_native_event=*/false);
diff --git a/ash/system/accessibility/switch_access/switch_access_menu_button.h b/ash/system/accessibility/switch_access/switch_access_menu_button.h
index db6cf27..953fcb8 100644
--- a/ash/system/accessibility/switch_access/switch_access_menu_button.h
+++ b/ash/system/accessibility/switch_access/switch_access_menu_button.h
@@ -35,9 +35,6 @@
 
   static constexpr int kWidthDip = 80;
 
-  // views::View:
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-
  private:
   friend class SwitchAccessMenuBubbleControllerTest;
 
diff --git a/ash/system/unified/quick_settings_header.cc b/ash/system/unified/quick_settings_header.cc
index f86eb914d..cca9192 100644
--- a/ash/system/unified/quick_settings_header.cc
+++ b/ash/system/unified/quick_settings_header.cc
@@ -8,6 +8,7 @@
 
 #include "ash/ash_element_identifiers.h"
 #include "ash/constants/quick_settings_catalogs.h"
+#include "ash/login/ui/lock_screen.h"
 #include "ash/public/cpp/ash_view_ids.h"
 #include "ash/public/cpp/session/session_observer.h"
 #include "ash/public/cpp/system_tray_client.h"
@@ -74,14 +75,6 @@
 constexpr auto kManagedStateBorderInsets = gfx::Insets::TLBR(0, 12, 0, 12);
 constexpr gfx::Size kManagedStateImageSize(20, 20);
 
-// Shows enterprise managed device information.
-void ShowEnterpriseInfo(UnifiedSystemTrayController* controller,
-                        const ui::Event& event) {
-  quick_settings_metrics_util::RecordQsButtonActivated(
-      QsButtonCatalogName::kManagedButton);
-  controller->HandleEnterpriseInfoAction();
-}
-
 // Shows account settings in OS settings, which includes a link to install or
 // open the Family Link app to see supervision settings.
 void ShowAccountSettings() {
@@ -96,7 +89,7 @@
   METADATA_HEADER(ManagedStateView, views::Button)
 
  public:
-  ManagedStateView(PressedCallback callback,
+  ManagedStateView(base::OnceClosure callback,
                    int label_id,
                    const gfx::VectorIcon& icon)
       : views::Button(std::move(callback)), icon_(icon) {
@@ -188,8 +181,11 @@
 
  public:
   explicit EnterpriseManagedView(UnifiedSystemTrayController* controller)
-      : ManagedStateView(base::BindRepeating(&ShowEnterpriseInfo,
-                                             base::Unretained(controller)),
+      : ManagedStateView(base::BindRepeating(
+                             &QuickSettingsHeader::ShowEnterpriseInfo,
+                             base::Unretained(controller),
+                             base::FeatureList::IsEnabled(
+                                 ash::features::kImprovedManagementDisclosure)),
                          IDS_ASH_ENTERPRISE_DEVICE_MANAGED_SHORT,
                          kQuickSettingsManagedIcon) {
     DCHECK(Shell::Get());
@@ -402,6 +398,22 @@
   }
 }
 
+// static
+void QuickSettingsHeader::ShowEnterpriseInfo(
+    UnifiedSystemTrayController* controller,
+    bool showManagementDisclosureDialog) {
+  quick_settings_metrics_util::RecordQsButtonActivated(
+      QsButtonCatalogName::kManagedButton);
+  // Show the new disclosure when on the login/lock screen and feature is
+  // enabled.
+  if (Shell::Get()->session_controller()->IsUserSessionBlocked() &&
+      showManagementDisclosureDialog) {
+    LockScreen::Get()->ShowManagementDisclosureDialog();
+  } else {
+    controller->HandleEnterpriseInfoAction();
+  }
+}
+
 BEGIN_METADATA(QuickSettingsHeader)
 END_METADATA
 
diff --git a/ash/system/unified/quick_settings_header.h b/ash/system/unified/quick_settings_header.h
index 3470970..9fdbec0 100644
--- a/ash/system/unified/quick_settings_header.h
+++ b/ash/system/unified/quick_settings_header.h
@@ -42,6 +42,10 @@
 
   EolNoticeQuickSettingsView* eol_notice_for_test() { return eol_notice_; }
 
+  // Shows enterprise managed device information.
+  static void ShowEnterpriseInfo(UnifiedSystemTrayController* controller,
+                                 bool showManagementDisclosureDialog);
+
   views::View* GetManagedButtonForTest();
   views::View* GetSupervisedButtonForTest();
   views::Label* GetManagedButtonLabelForTest();
diff --git a/ash/system/unified/quick_settings_header_unittest.cc b/ash/system/unified/quick_settings_header_unittest.cc
index 90e4bf5..6ba35c9e 100644
--- a/ash/system/unified/quick_settings_header_unittest.cc
+++ b/ash/system/unified/quick_settings_header_unittest.cc
@@ -6,6 +6,10 @@
 
 #include <memory>
 
+#include "ash/login/ui/fake_login_detachable_base_model.h"
+#include "ash/login/ui/lock_contents_view_test_api.h"
+#include "ash/login/ui/lock_screen.h"
+#include "ash/login/ui/login_test_base.h"
 #include "ash/public/cpp/ash_view_ids.h"
 #include "ash/public/cpp/test/test_system_tray_client.h"
 #include "ash/session/session_controller_impl.h"
@@ -19,8 +23,10 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/test_shell_delegate.h"
 #include "base/check.h"
+#include "base/feature_list.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "chromeos/ash/components/login/auth/auth_events_recorder.h"
 #include "components/user_manager/user_type.h"
 #include "components/version_info/channel.h"
 #include "ui/events/test/event_generator.h"
@@ -367,4 +373,52 @@
   EXPECT_EQ(GetSystemTrayClient()->show_account_settings_count(), 1);
 }
 
+TEST_F(QuickSettingsHeaderTest, ShowManagementDisclosure) {
+  // This test relies on the lock screen actually being created (and creating
+  // the lock screen requires the existence of an `AuthEventsRecorder`).
+  std::unique_ptr<AuthEventsRecorder> auth_events_recorder =
+      AuthEventsRecorder::CreateForTesting();
+
+  // Setup to lock screen.
+  TestSessionControllerClient* client = GetSessionControllerClient();
+  client->Reset();
+  GetSessionControllerClient()->set_show_lock_screen_views(true);
+  client->LockScreen();
+  client->SetSessionState(session_manager::SessionState::LOCKED);
+
+  // Create new lock screen for lock_contents.
+  ash::LockScreen::Get()->Destroy();
+  LockScreen::Show(LockScreen::ScreenType::kLock);
+
+  QuickSettingsHeader::ShowEnterpriseInfo(controller_.get(), true);
+  LockContentsViewTestApi lock_contents(
+      LockScreen::TestApi(LockScreen::Get()).contents_view());
+
+  EXPECT_TRUE(lock_contents.management_disclosure_dialog());
+}
+
+TEST_F(QuickSettingsHeaderTest, DoNotShowManagementDisclosure) {
+  // This test relies on the lock screen actually being created (and creating
+  // the lock screen requires the existence of an `AuthEventsRecorder`).
+  std::unique_ptr<AuthEventsRecorder> auth_events_recorder =
+      AuthEventsRecorder::CreateForTesting();
+
+  // Setup to lock screen.
+  TestSessionControllerClient* client = GetSessionControllerClient();
+  client->Reset();
+  GetSessionControllerClient()->set_show_lock_screen_views(true);
+  client->LockScreen();
+  client->SetSessionState(session_manager::SessionState::LOCKED);
+
+  // Create new lock screen for lock_contents.
+  ash::LockScreen::Get()->Destroy();
+  LockScreen::Show(LockScreen::ScreenType::kLock);
+
+  QuickSettingsHeader::ShowEnterpriseInfo(controller_.get(), false);
+  LockContentsViewTestApi lock_contents(
+      LockScreen::TestApi(LockScreen::Get()).contents_view());
+
+  EXPECT_FALSE(lock_contents.management_disclosure_dialog());
+}
+
 }  // namespace ash
diff --git a/ash/webui/.eslintrc.js b/ash/webui/.eslintrc.js
deleted file mode 100644
index 0c83822..0000000
--- a/ash/webui/.eslintrc.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  'rules' : {
-    'comma-dangle' : ['error', 'always-multiline'],
-    'no-console' : 'off',
-
-    // Turn off since there are too many imports of 'Polymer'. Remove if/when
-    // everything under this folder is migrated to PolymerElement.
-    'no-restricted-imports' : 'off',
-
-    // Turn off until all violations under this folder are fixed. This was
-    // done for other parts of the codebase in http://crbug.com/1494527
-    '@typescript-eslint/consistent-type-imports': 'off',
-  },
-};
diff --git a/ash/webui/common/resources/network/network_proxy.ts b/ash/webui/common/resources/network/network_proxy.ts
index 432f8e70..4b5feab 100644
--- a/ash/webui/common/resources/network/network_proxy.ts
+++ b/ash/webui/common/resources/network/network_proxy.ts
@@ -535,4 +535,10 @@
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    [NetworkProxyElement.is]: NetworkProxyElement;
+  }
+}
+
 customElements.define(NetworkProxyElement.is, NetworkProxyElement);
diff --git a/ash/webui/diagnostics_ui/resources/.eslintrc.js b/ash/webui/diagnostics_ui/resources/.eslintrc.js
deleted file mode 100644
index e7286a2..0000000
--- a/ash/webui/diagnostics_ui/resources/.eslintrc.js
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Disable clang-format because it produces odd formatting.
-// clang-format off
-module.exports = {
-    'ignorePatterns': ['**/*.js'],
-    'rules': {
-      '@typescript-eslint/explicit-function-return-type': ['error'],
-    },
-    'overrides': [{
-      'files': ['**/*.ts'],
-      'parser':
-        '../../../../third_party/node/node_modules/@typescript-eslint/parser/dist/index.js',
-      'parserOptions': {
-        tsconfigRootDir: __dirname,
-      },
-      'plugins': ['@typescript-eslint'],
-      'rules': {
-        '@typescript-eslint/naming-convention':
-          ['error',
-            {
-              selector: ['classMethod', 'classProperty'],
-              format: ['camelCase'],
-              modifiers: ['private'],
-              trailingUnderscore: 'forbid',
-            },
-          ],
-      },
-    }],
-  };
-// clang-format on
diff --git a/ash/webui/media_app_ui/.eslintrc.js b/ash/webui/media_app_ui/.eslintrc.js
deleted file mode 100644
index 572bf31..0000000
--- a/ash/webui/media_app_ui/.eslintrc.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  'env': {
-    'browser': true,
-    'es6': true,
-  },
-  'parserOptions': {
-    'ecmaVersion': 2018,
-    'sourceType': 'module',
-  },
-  'rules': {
-    'eqeqeq': ['error', 'always', {'null': 'ignore'}],
-  },
-};
diff --git a/ash/webui/os_feedback_ui/backend/help_content_provider.cc b/ash/webui/os_feedback_ui/backend/help_content_provider.cc
index 49c0f7e5..2bd2d11 100644
--- a/ash/webui/os_feedback_ui/backend/help_content_provider.cc
+++ b/ash/webui/os_feedback_ui/backend/help_content_provider.cc
@@ -76,7 +76,7 @@
           destination: GOOGLE_OWNED_SERVICE
           internal {
             contacts {
-              email: "cros-feedback-app@google.com"
+              email: "cros-device-enablement@google.com"
             }
           }
           user_data {
diff --git a/ash/webui/print_management/resources/.eslintrc.js b/ash/webui/print_management/resources/.eslintrc.js
deleted file mode 100644
index 18e6dac..0000000
--- a/ash/webui/print_management/resources/.eslintrc.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Disable clang-format because it produces odd formatting.
-// clang-format off
-module.exports = {
-  'rules': {
-    '@typescript-eslint/explicit-function-return-type': ['error'],
-  },
-  'overrides': [{
-    'files': ['**/*.ts'],
-    'parser':
-      '../../../../third_party/node/node_modules/@typescript-eslint/parser/dist/index.js',
-    'parserOptions': {
-      tsconfigRootDir: __dirname,
-    },
-    'plugins': ['@typescript-eslint'],
-    'rules': {
-      '@typescript-eslint/naming-convention':
-        ['error',
-          {
-            selector: ['classMethod', 'classProperty'],
-            format: ['camelCase'],
-            modifiers: ['private'],
-            trailingUnderscore: 'forbid',
-          },
-        ],
-    },
-  }],
-};
-// clang-format on
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service.cc b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
index 20b1fbc..d94ef57 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
@@ -447,12 +447,11 @@
   TransitionNextStateGeneric(std::move(callback));
 }
 
-void ShimlessRmaService::ChooseManuallyDisableWriteProtect(
-    ChooseManuallyDisableWriteProtectCallback callback) {
+void ShimlessRmaService::SetManuallyDisableWriteProtect(
+    SetManuallyDisableWriteProtectCallback callback) {
   if (state_proto_.state_case() != rmad::RmadState::kWpDisableMethod) {
-    LOG(ERROR)
-        << "ChooseManuallyDisableWriteProtect called from incorrect state "
-        << state_proto_.state_case();
+    LOG(ERROR) << "SetManuallyDisableWriteProtect called from incorrect state "
+               << state_proto_.state_case();
     std::move(callback).Run(CreateStateResultForInvalidRequest());
     return;
   }
@@ -461,10 +460,10 @@
   TransitionNextStateGeneric(std::move(callback));
 }
 
-void ShimlessRmaService::ChooseRsuDisableWriteProtect(
-    ChooseRsuDisableWriteProtectCallback callback) {
+void ShimlessRmaService::SetRsuDisableWriteProtect(
+    SetRsuDisableWriteProtectCallback callback) {
   if (state_proto_.state_case() != rmad::RmadState::kWpDisableMethod) {
-    LOG(ERROR) << "ChooseRsuDisableWriteProtect called from incorrect state "
+    LOG(ERROR) << "SetRsuDisableWriteProtect called from incorrect state "
                << state_proto_.state_case();
     std::move(callback).Run(CreateStateResultForInvalidRequest());
     return;
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service.h b/ash/webui/shimless_rma/backend/shimless_rma_service.h
index 371a84b0..f482ef841 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service.h
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service.h
@@ -54,10 +54,10 @@
   void SetSameOwner(SetSameOwnerCallback callback) override;
   void SetDifferentOwner(SetDifferentOwnerCallback callback) override;
   void SetWipeDevice(bool wipe_device, SetWipeDeviceCallback) override;
-  void ChooseManuallyDisableWriteProtect(
-      ChooseManuallyDisableWriteProtectCallback callback) override;
-  void ChooseRsuDisableWriteProtect(
-      ChooseRsuDisableWriteProtectCallback callback) override;
+  void SetManuallyDisableWriteProtect(
+      SetManuallyDisableWriteProtectCallback callback) override;
+  void SetRsuDisableWriteProtect(
+      SetRsuDisableWriteProtectCallback callback) override;
   void GetRsuDisableWriteProtectChallenge(
       GetRsuDisableWriteProtectChallengeCallback callback) override;
   void GetRsuDisableWriteProtectHwid(
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc b/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
index 492cd10..12b614a 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
@@ -1322,7 +1322,7 @@
       }));
   run_loop.RunUntilIdle();
 
-  shimless_rma_provider_->ChooseManuallyDisableWriteProtect(
+  shimless_rma_provider_->SetManuallyDisableWriteProtect(
       base::BindLambdaForTesting([&](mojom::StateResultPtr state_result_ptr) {
         EXPECT_EQ(state_result_ptr->state, mojom::State::kChooseDestination);
         EXPECT_EQ(state_result_ptr->error, rmad::RmadErrorCode::RMAD_ERROR_OK);
@@ -1344,7 +1344,7 @@
       }));
   run_loop.RunUntilIdle();
 
-  shimless_rma_provider_->ChooseManuallyDisableWriteProtect(
+  shimless_rma_provider_->SetManuallyDisableWriteProtect(
       base::BindLambdaForTesting([&](mojom::StateResultPtr state_result_ptr) {
         EXPECT_EQ(state_result_ptr->state, mojom::State::kChooseDestination);
         EXPECT_EQ(state_result_ptr->error,
@@ -1375,7 +1375,7 @@
       }));
   run_loop.RunUntilIdle();
 
-  shimless_rma_provider_->ChooseRsuDisableWriteProtect(
+  shimless_rma_provider_->SetRsuDisableWriteProtect(
       base::BindLambdaForTesting([&](mojom::StateResultPtr state_result_ptr) {
         EXPECT_EQ(state_result_ptr->state, mojom::State::kChooseDestination);
         EXPECT_EQ(state_result_ptr->error, rmad::RmadErrorCode::RMAD_ERROR_OK);
@@ -1396,7 +1396,7 @@
       }));
   run_loop.RunUntilIdle();
 
-  shimless_rma_provider_->ChooseRsuDisableWriteProtect(
+  shimless_rma_provider_->SetRsuDisableWriteProtect(
       base::BindLambdaForTesting([&](mojom::StateResultPtr state_result_ptr) {
         EXPECT_EQ(state_result_ptr->state, mojom::State::kChooseDestination);
         EXPECT_EQ(state_result_ptr->error,
diff --git a/ash/webui/shimless_rma/mojom/shimless_rma.mojom b/ash/webui/shimless_rma/mojom/shimless_rma.mojom
index 17f9f2d..70d6ab3 100644
--- a/ash/webui/shimless_rma/mojom/shimless_rma.mojom
+++ b/ash/webui/shimless_rma/mojom/shimless_rma.mojom
@@ -74,7 +74,6 @@
 // This must remain in sync with RmadErrorCode in
 // //third_party/cros_system_api/dbus/rmad/rmad.proto.
 // See shimless_rma_mojom_traits.cc\h.
-// TODO(crbug.com/1218175): Remove Rmad prefix from name.
 enum RmadErrorCode {
   // 0 is the default value. It should never be used.
   kNotSet = 0,
@@ -604,15 +603,11 @@
   // Choose to disabled HWWP manually e.g. by disconnecting the battery
   // If successful returns the next state and kOk followed by a signalling the
   // HardwareWriteProtectionStateObserver when HWWP is disabled.
-  // TODO(crbug.com/1218175): Rename SetManuallyDisableWriteProtect for
-  // consistency with other methods.
-  ChooseManuallyDisableWriteProtect()
+  SetManuallyDisableWriteProtect()
       => (StateResult state_result);
   // Choose to disable HWWP using the RSU code method.
   // Returns the next state to display and an error code.
-  // TODO(crbug.com/1218175): Rename SetRsuDisableWriteProtect for
-  // consistency with other methods.
-  ChooseRsuDisableWriteProtect()
+  SetRsuDisableWriteProtect()
       => (StateResult state_result);
 
   ///////////////////////////////////////
diff --git a/ash/webui/shimless_rma/resources/fake_shimless_rma_service.ts b/ash/webui/shimless_rma/resources/fake_shimless_rma_service.ts
index 666d3a14..45176e1 100644
--- a/ash/webui/shimless_rma/resources/fake_shimless_rma_service.ts
+++ b/ash/webui/shimless_rma/resources/fake_shimless_rma_service.ts
@@ -326,15 +326,15 @@
         'manualDisableWriteProtectAvailable', {available: available});
   }
 
-  chooseManuallyDisableWriteProtect(): Promise<{stateResult: StateResult}> {
+  setManuallyDisableWriteProtect(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
-        'chooseManuallyDisableWriteProtect',
+        'setManuallyDisableWriteProtect',
         State.kChooseWriteProtectDisableMethod);
   }
 
-  chooseRsuDisableWriteProtect(): Promise<{stateResult: StateResult}> {
+  setRsuDisableWriteProtect(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
-        'chooseRsuDisableWriteProtect', State.kChooseWriteProtectDisableMethod);
+        'setRsuDisableWriteProtect', State.kChooseWriteProtectDisableMethod);
   }
 
   getRsuDisableWriteProtectChallenge(): Promise<{challenge: string}> {
@@ -1168,8 +1168,8 @@
     this.methods.register('setDifferentOwner');
     this.methods.register('setWipeDevice');
 
-    this.methods.register('chooseManuallyDisableWriteProtect');
-    this.methods.register('chooseRsuDisableWriteProtect');
+    this.methods.register('setManuallyDisableWriteProtect');
+    this.methods.register('setRsuDisableWriteProtect');
     this.methods.register('getRsuDisableWriteProtectChallenge');
     this.methods.register('getRsuDisableWriteProtectHwid');
     this.methods.register('getRsuDisableWriteProtectChallengeQrCode');
diff --git a/ash/webui/shimless_rma/resources/onboarding_choose_wp_disable_method_page.ts b/ash/webui/shimless_rma/resources/onboarding_choose_wp_disable_method_page.ts
index c3e1f009..41bc492 100644
--- a/ash/webui/shimless_rma/resources/onboarding_choose_wp_disable_method_page.ts
+++ b/ash/webui/shimless_rma/resources/onboarding_choose_wp_disable_method_page.ts
@@ -71,9 +71,9 @@
 
   onNextButtonClick(): Promise<{stateResult: StateResult}> {
     if (this.hwwpMethod === 'hwwpDisableMethodManual') {
-      return this.shimlessRmaService.chooseManuallyDisableWriteProtect();
+      return this.shimlessRmaService.setManuallyDisableWriteProtect();
     } else if (this.hwwpMethod === 'hwwpDisableMethodRsu') {
-      return this.shimlessRmaService.chooseRsuDisableWriteProtect();
+      return this.shimlessRmaService.setRsuDisableWriteProtect();
     } else {
       return Promise.reject(new Error('No disable method selected'));
     }
diff --git a/ash/webui/shortcut_customization_ui/resources/.eslintrc.js b/ash/webui/shortcut_customization_ui/resources/.eslintrc.js
deleted file mode 100644
index 626bd45c..0000000
--- a/ash/webui/shortcut_customization_ui/resources/.eslintrc.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Disable clang-format because it produces odd formatting.
-// clang-format off
-module.exports = {
-    'rules': {
-      '@typescript-eslint/explicit-function-return-type': ['error'],
-    },
-    'overrides': [{
-      'files': ['**/*.ts'],
-      'parser':
-        '../../../../third_party/node/node_modules/@typescript-eslint/parser/dist/index.js',
-      'parserOptions': {
-        tsconfigRootDir: __dirname,
-      },
-      'plugins': ['@typescript-eslint'],
-      'rules': {
-        '@typescript-eslint/naming-convention':
-          ['error',
-            {
-              selector: ['classMethod', 'classProperty'],
-              format: ['camelCase'],
-              modifiers: ['private'],
-              trailingUnderscore: 'forbid',
-            },
-          ],
-      },
-    }],
-  };
-// clang-format on
diff --git a/ash/webui/system_apps/public/system_web_app_type.h b/ash/webui/system_apps/public/system_web_app_type.h
index b5be1bfd1..a01faf7 100644
--- a/ash/webui/system_apps/public/system_web_app_type.h
+++ b/ash/webui/system_apps/public/system_web_app_type.h
@@ -72,7 +72,7 @@
   // feedback report on Chrome OS.
   //
   // Source: //ash/webui/os_feedback_ui
-  // contact: cros-feedback-app@google.com
+  // contact: cros-device-enablement@google.com
   OS_FEEDBACK = 19,
 
   // Projector aka Screencast (go/projector-player-dd) aims to make it simple
diff --git a/ash/wm/coral/coral_controller.cc b/ash/wm/coral/coral_controller.cc
index c800bf02..661c9a93 100644
--- a/ash/wm/coral/coral_controller.cc
+++ b/ash/wm/coral/coral_controller.cc
@@ -7,9 +7,21 @@
 #include "ash/public/cpp/coral_delegate.h"
 #include "ash/shell.h"
 #include "ash/wm/desks/desks_controller.h"
+#include "chromeos/ash/components/mojo_service_manager/connection.h"
+#include "chromeos/ash/services/coral/public/mojom/coral_service.mojom.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/cros_system_api/mojo/service_constants.h"
 
 namespace ash {
 
+namespace {
+constexpr int kMinItemsInGroup = 4;
+constexpr int kMaxItemsInGroup = 10;
+constexpr int kMaxGroupsToGenerate = 2;
+// Too many items in 1 request could result in poor performance.
+constexpr size_t kMaxItemsInRequest = 100;
+}  // namespace
+
 CoralRequest::CoralRequest() = default;
 
 CoralRequest::~CoralRequest() = default;
@@ -24,14 +36,97 @@
 
 void CoralController::GenerateContentGroups(const CoralRequest& request,
                                             CoralResponseCallback callback) {
-  // Not implemented yet.
-  std::move(callback).Run(nullptr);
+  // There couldn't be valid groups, skip generating and return an empty
+  // response.
+  if (request.content().size() < kMinItemsInGroup) {
+    std::move(callback).Run(std::make_unique<CoralResponse>());
+    return;
+  }
+
+  EnsureCoralService();
+  if (!coral_service_) {
+    LOG(ERROR) << "Failed to connect to coral service.";
+    std::move(callback).Run(nullptr);
+    return;
+  }
+
+  auto group_request = coral::mojom::GroupRequest::New();
+  group_request->embedding_options = coral::mojom::EmbeddingOptions::New();
+  group_request->clustering_options = coral::mojom::ClusteringOptions::New();
+  group_request->clustering_options->min_items_in_cluster = kMinItemsInGroup;
+  group_request->clustering_options->max_items_in_cluster = kMaxItemsInGroup;
+  group_request->clustering_options->max_clusters = kMaxGroupsToGenerate;
+  group_request->title_generation_options =
+      coral::mojom::TitleGenerationOptions::New();
+  const size_t items_in_request =
+      std::min(request.content().size(), kMaxItemsInRequest);
+  for (size_t i = 0; i < items_in_request; i++) {
+    group_request->entities.push_back(request.content()[i]->Clone());
+  }
+  coral_service_->Group(
+      std::move(group_request),
+      base::BindOnce(&CoralController::HandleGroupResult,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
 }
 
 void CoralController::CacheEmbeddings(const CoralRequest& request,
                                       base::OnceCallback<void(bool)> callback) {
-  // Not implemented yet.
-  std::move(callback).Run(false);
+  EnsureCoralService();
+  if (!coral_service_) {
+    LOG(ERROR) << "Failed to connect to coral service.";
+    std::move(callback).Run(false);
+    return;
+  }
+
+  auto cache_embeddings_request = coral::mojom::CacheEmbeddingsRequest::New();
+  cache_embeddings_request->embedding_options =
+      coral::mojom::EmbeddingOptions::New();
+  for (const auto& entity : request.content()) {
+    cache_embeddings_request->entities.push_back(entity->Clone());
+  }
+
+  coral_service_->CacheEmbeddings(
+      std::move(cache_embeddings_request),
+      base::BindOnce(&CoralController::HandleCacheEmbeddingsResult,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void CoralController::EnsureCoralService() {
+  if (coral_service_) {
+    return;
+  }
+  auto pipe_handle = coral_service_.BindNewPipeAndPassReceiver().PassPipe();
+  coral_service_.reset_on_disconnect();
+  ash::mojo_service_manager::GetServiceManagerProxy()->Request(
+      chromeos::mojo_services::kCrosCoralService, std::nullopt,
+      std::move(pipe_handle));
+}
+
+void CoralController::HandleGroupResult(CoralResponseCallback callback,
+                                        coral::mojom::GroupResultPtr result) {
+  if (result->is_error()) {
+    LOG(ERROR) << "Coral group request failed with CoralError code: "
+               << static_cast<int>(result->get_error());
+    std::move(callback).Run(nullptr);
+    return;
+  }
+  coral::mojom::GroupResponsePtr group_response =
+      std::move(result->get_response());
+  auto response = std::make_unique<CoralResponse>();
+  response->set_groups(std::move(group_response->groups));
+  std::move(callback).Run(std::move(response));
+}
+
+void CoralController::HandleCacheEmbeddingsResult(
+    base::OnceCallback<void(bool)> callback,
+    coral::mojom::CacheEmbeddingsResultPtr result) {
+  if (result->is_error()) {
+    LOG(ERROR) << "Coral cache embeddings request failed with CoralError code: "
+               << static_cast<int>(result->get_error());
+    std::move(callback).Run(false);
+    return;
+  }
+  std::move(callback).Run(true);
 }
 
 void CoralController::OpenNewDeskWithGroup(CoralResponse::Group group) {
diff --git a/ash/wm/coral/coral_controller.h b/ash/wm/coral/coral_controller.h
index a971bfe..3cdc1e2 100644
--- a/ash/wm/coral/coral_controller.h
+++ b/ash/wm/coral/coral_controller.h
@@ -11,6 +11,7 @@
 #include "ash/ash_export.h"
 #include "base/memory/weak_ptr.h"
 #include "chromeos/ash/services/coral/public/mojom/coral_service.mojom.h"
+#include "mojo/public/cpp/bindings/remote.h"
 
 namespace ash {
 
@@ -77,6 +78,24 @@
   void OpenNewDeskWithGroup(CoralResponse::Group group);
 
  private:
+  using CoralService = coral::mojom::CoralService;
+
+  void EnsureCoralService();
+
+  // Used as the callback of mojom::CoralService::Group.
+  void HandleGroupResult(CoralResponseCallback callback,
+                         coral::mojom::GroupResultPtr result);
+
+  // Used as the callback of mojom::CoralService::CacheEmbeddings. `callback` is
+  // the callback passed from `CoralController::CacheEmbeddings`, which should
+  // be triggered with a bool indicating whether the CacheEmbeddings operation
+  // was successful.
+  void HandleCacheEmbeddingsResult(
+      base::OnceCallback<void(bool)> callback,
+      coral::mojom::CacheEmbeddingsResultPtr result);
+
+  mojo::Remote<CoralService> coral_service_;
+
   base::WeakPtrFactory<CoralController> weak_factory_{this};
 };
 
diff --git a/ash/wm/overview/birch/birch_bar_util.cc b/ash/wm/overview/birch/birch_bar_util.cc
index 32b544b7..d2c0b74 100644
--- a/ash/wm/overview/birch/birch_bar_util.cc
+++ b/ash/wm/overview/birch/birch_bar_util.cc
@@ -5,6 +5,7 @@
 #include "ash/wm/overview/birch/birch_bar_util.h"
 
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/icon_button.h"
 #include "ash/style/pill_button.h"
 #include "ash/style/typography.h"
 #include "ash/wm/overview/birch/birch_bar_context_menu_model.h"
@@ -12,6 +13,7 @@
 #include "ash/wm/overview/birch/birch_bar_view.h"
 #include "ash/wm/overview/birch/birch_chip_button.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/layout/box_layout_view.h"
 #include "ui/views/view_class_properties.h"
@@ -40,6 +42,18 @@
   return button;
 }
 
+std::unique_ptr<views::Button> CreateCoralAddonButton(
+    views::Button::PressedCallback callback,
+    const gfx::VectorIcon& button_icon,
+    const std::u16string& accessible_name) {
+  auto button = std::make_unique<IconButton>(
+      std::move(callback), IconButton::Type::kMediumProminent, &button_icon,
+      accessible_name, /*is_togglable=*/true, /*has_border=*/true);
+  button->SetProperty(views::kMarginsKey, kAddonMargins);
+  button->SetBackgroundColor(cros_tokens::kCrosSysSystemBaseElevated);
+  return button;
+}
+
 std::unique_ptr<views::View> CreateWeatherTemperatureView(
     const std::u16string& temp_str,
     bool fahrenheit) {
diff --git a/ash/wm/overview/birch/birch_bar_util.h b/ash/wm/overview/birch/birch_bar_util.h
index 7f6819c..d5c6378 100644
--- a/ash/wm/overview/birch/birch_bar_util.h
+++ b/ash/wm/overview/birch/birch_bar_util.h
@@ -19,6 +19,13 @@
     views::Button::PressedCallback callback,
     const std::u16string& label);
 
+// Creates a button for the glanceables chip with given `callback` and
+// `button_icon`, e.g. the expand/collapse button of coral chip.
+std::unique_ptr<views::Button> CreateCoralAddonButton(
+    views::Button::PressedCallback callback,
+    const gfx::VectorIcon& button_icon,
+    const std::u16string& accessible_name);
+
 // Creates a weather temperature view which consists of two labels, one is for
 // the temperature degree and the other is for the degree unit (Fahrenheit v.s.
 // Celsius).
diff --git a/ash/wm/overview/birch/birch_chip_button.cc b/ash/wm/overview/birch/birch_chip_button.cc
index 21ef966..d93aa65 100644
--- a/ash/wm/overview/birch/birch_chip_button.cc
+++ b/ash/wm/overview/birch/birch_chip_button.cc
@@ -16,6 +16,7 @@
 #include "ash/wm/overview/birch/birch_chip_context_menu_model.h"
 #include "ash/wm/overview/birch/tab_app_selection_host.h"
 #include "base/types/cxx23_to_underlying.h"
+#include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/models/image_model.h"
@@ -205,21 +206,27 @@
   const auto addon_type = item_->GetAddonType();
   // Add add-ons according to the add-on type.
   switch (addon_type) {
-    case BirchAddonType::kButton:
-    case BirchAddonType::kCoralButton: {
-      // Coral item works different since it triggers a new overview view.
-      const bool is_coral = addon_type == BirchAddonType::kCoralButton;
-      base::RepeatingClosure callback =
-          is_coral ? base::BindRepeating(&BirchChipButton::OnCoralAddonClicked,
-                                         base::Unretained(this))
-                   : base::BindRepeating(&BirchItem::PerformAddonAction,
-                                         base::Unretained(item_));
+    case BirchAddonType::kButton: {
+      base::RepeatingClosure callback = base::BindRepeating(
+          &BirchItem::PerformAddonAction, base::Unretained(item_));
       auto button = birch_bar_util::CreateAddonButton(std::move(callback),
                                                       *item_->addon_label());
       button->SetTooltipText(item->GetAddonAccessibleName());
       SetAddon(std::move(button));
       break;
     }
+    case BirchAddonType::kCoralButton: {
+      // Coral item works different since it triggers a new overview view.
+      base::RepeatingClosure callback = base::BindRepeating(
+          &BirchChipButton::OnCoralAddonClicked, base::Unretained(this));
+      // Coral chip's addon button contains no text.
+      auto button = birch_bar_util::CreateCoralAddonButton(
+          std::move(callback), vector_icons::kCaretUpIcon,
+          item->GetAddonAccessibleName());
+      button->SetTooltipText(item->GetAddonAccessibleName());
+      SetAddon(std::move(button));
+      break;
+    }
     case BirchAddonType::kWeatherTempLabelC:
     case BirchAddonType::kWeatherTempLabelF:
       SetAddon(birch_bar_util::CreateWeatherTemperatureView(
diff --git a/ash/wm/overview/birch/birch_chip_button.h b/ash/wm/overview/birch/birch_chip_button.h
index ebe7844..92c15da 100644
--- a/ash/wm/overview/birch/birch_chip_button.h
+++ b/ash/wm/overview/birch/birch_chip_button.h
@@ -38,6 +38,9 @@
   BirchChipButton& operator=(const BirchChipButton&) = delete;
   ~BirchChipButton() override;
 
+  views::View* addon_view() { return addon_view_; }
+  const views::View* addon_view() const { return addon_view_; }
+
   TabAppSelectionHost* tab_app_selection_widget() {
     return tab_app_selection_widget_.get();
   }
@@ -51,12 +54,11 @@
   // ui::SimpleMenuModel::Delegate:
   void ExecuteCommand(int command_id, int event_flags) override;
 
-  const views::View* addon_view_for_testing() const { return addon_view_; }
-
  private:
   FRIEND_TEST_ALL_PREFIXES(BirchBarTest, NoCrashOnSettingIconAfterShutdown);
   FRIEND_TEST_ALL_PREFIXES(BirchBarTest, UpdateLostMediaChip);
   class ChipMenuController;
+  friend class TabAppSelectionHost;
   friend class TabAppSelectionViewTest;
 
   void SetAddon(std::unique_ptr<views::View> addon_view);
diff --git a/ash/wm/overview/birch/tab_app_selection_host.cc b/ash/wm/overview/birch/tab_app_selection_host.cc
index 4514c82..ea579e305 100644
--- a/ash/wm/overview/birch/tab_app_selection_host.cc
+++ b/ash/wm/overview/birch/tab_app_selection_host.cc
@@ -6,11 +6,14 @@
 
 #include "ash/birch/birch_coral_item.h"
 #include "ash/public/cpp/window_properties.h"
+#include "ash/style/icon_button.h"
 #include "ash/wm/overview/birch/birch_chip_button.h"
 #include "ash/wm/overview/birch/birch_chip_button_base.h"
 #include "ash/wm/overview/birch/tab_app_selection_view.h"
 #include "ash/wm/window_properties.h"
+#include "components/vector_icons/vector_icons.h"
 #include "ui/aura/window.h"
+#include "ui/views/controls/button/label_button.h"
 #include "ui/views/view_utils.h"
 #include "ui/views/widget/widget_delegate.h"
 
@@ -54,6 +57,15 @@
       ->ProcessKeyEvent(event);
 }
 
+void TabAppSelectionHost::OnNativeWidgetVisibilityChanged(bool visible) {
+  views::Widget::OnNativeWidgetVisibilityChanged(visible);
+  views::AsViewClass<IconButton>(owner_->addon_view())
+      ->SetImageModel(
+          BirchChipButtonBase::ButtonState::STATE_NORMAL,
+          ui::ImageModel::FromVectorIcon(visible ? vector_icons::kCaretDownIcon
+                                                 : vector_icons::kCaretUpIcon));
+}
+
 gfx::Rect TabAppSelectionHost::GetDesiredBoundsInScreen() {
   const int preferred_height = GetContentsView()->GetPreferredSize().height();
   gfx::Rect selector_bounds = owner_->GetBoundsInScreen();
diff --git a/ash/wm/overview/birch/tab_app_selection_host.h b/ash/wm/overview/birch/tab_app_selection_host.h
index 30221ff..903030ffe 100644
--- a/ash/wm/overview/birch/tab_app_selection_host.h
+++ b/ash/wm/overview/birch/tab_app_selection_host.h
@@ -19,6 +19,9 @@
 
   void ProcessKeyEvent(ui::KeyEvent* event);
 
+  // views::Widget:
+  void OnNativeWidgetVisibilityChanged(bool visible) override;
+
   const BirchChipButton* owner_for_testing() const { return owner_; }
 
  private:
diff --git a/ash/wm/overview/birch/tab_app_selection_view_unittest.cc b/ash/wm/overview/birch/tab_app_selection_view_unittest.cc
index c7ff3a45..d8f423d 100644
--- a/ash/wm/overview/birch/tab_app_selection_view_unittest.cc
+++ b/ash/wm/overview/birch/tab_app_selection_view_unittest.cc
@@ -81,7 +81,7 @@
     auto* coral_button = views::AsViewClass<BirchChipButton>(birch_chips[0]);
     CHECK_EQ(BirchItemType::kCoral, coral_button->GetItem()->GetType());
 
-    LeftClickOn(coral_button->addon_view_for_testing());
+    LeftClickOn(coral_button->addon_view());
     return coral_button->tab_app_selection_widget_.get();
   }
 
@@ -98,10 +98,10 @@
   ASSERT_TRUE(menu);
   EXPECT_TRUE(menu->IsVisible());
 
-  LeftClickOn(menu->owner_for_testing()->addon_view_for_testing());
+  LeftClickOn(menu->owner_for_testing()->addon_view());
   EXPECT_FALSE(menu->IsVisible());
 
-  LeftClickOn(menu->owner_for_testing()->addon_view_for_testing());
+  LeftClickOn(menu->owner_for_testing()->addon_view());
   EXPECT_TRUE(menu->IsVisible());
 }
 
diff --git a/base/mac/process_requirement.cc b/base/mac/process_requirement.cc
index 379febc..ad3dc2cb 100644
--- a/base/mac/process_requirement.cc
+++ b/base/mac/process_requirement.cc
@@ -6,25 +6,32 @@
 
 #include <Kernel/kern/cs_blobs.h>
 #include <Security/Security.h>
+#include <mach/kern_return.h>
 #include <stdint.h>
 #include <sys/errno.h>
 
 #include <optional>
 
+#include "base/apple/mach_logging.h"
 #include "base/apple/osstatus_logging.h"
 #include "base/apple/scoped_cftyperef.h"
 #include "base/check_op.h"
 #include "base/containers/span.h"
+#include "base/features.h"
 #include "base/logging.h"
 #include "base/mac/code_signature.h"
 #include "base/mac/code_signature_spi.h"
+#include "base/mac/info_plist_data.h"
 #include "base/mac/mac_util.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/no_destructor.h"
 #include "base/notreached.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
+#include "base/task/thread_pool.h"
 #include "base/types/expected.h"
+#include "build/branding_buildflags.h"
 
 using base::apple::ScopedCFTypeRef;
 
@@ -208,6 +215,15 @@
   }
 }
 
+audit_token_t AuditTokenForCurrentProcess() {
+  audit_token_t token;
+  mach_msg_type_number_t count = TASK_AUDIT_TOKEN_COUNT;
+  kern_return_t kr = task_info(mach_task_self(), TASK_AUDIT_TOKEN,
+                               reinterpret_cast<task_info_t>(&token), &count);
+  MACH_CHECK(kr == KERN_SUCCESS, kr) << "task_info(TASK_AUDIT_TOKEN)";
+  return token;
+}
+
 }  // namespace
 
 ProcessRequirement::Builder::Builder() = default;
@@ -493,4 +509,70 @@
   return true;
 }
 
+// static
+void ProcessRequirement::MaybeGatherMetrics() {
+  static BASE_FEATURE(kGatherProcessRequirementMetrics,
+                      "GatherProcessRequirementMetrics",
+                      base::FEATURE_ENABLED_BY_DEFAULT);
+  if (base::FeatureList::IsEnabled(kGatherProcessRequirementMetrics)) {
+    base::ThreadPool::PostTask(
+        FROM_HERE,
+        {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+         base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+        base::BindOnce(&ProcessRequirement::GatherMetrics));
+  }
+}
+
+namespace {
+template <typename T>
+void RecordOperationHistogram(std::string histogram_prefix,
+                              base::expected<T, int> value,
+                              T expected_value_if_chrome) {
+  base::UmaHistogramSparse(histogram_prefix + ".Result", value.error_or(0));
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  if (value.has_value()) {
+    base::UmaHistogramBoolean(histogram_prefix + ".HasExpectedValue",
+                              *value == expected_value_if_chrome);
+  }
+#endif
+}
+}  // namespace
+
+// static
+void ProcessRequirement::GatherMetrics() {
+  RecordOperationHistogram("Mac.ProcessRequirement.TeamIdentifier",
+                           TeamIdentifierOfCurrentProcess(),
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+                           std::string("EQHXZ8M8AV")
+#else
+                           std::string()
+#endif
+  );
+
+  RecordOperationHistogram("Mac.ProcessRequirement.ValidationCategory",
+                           ValidationCategoryOfCurrentProcess(),
+                           ValidationCategory::DeveloperId);
+
+  RecordOperationHistogram("Mac.ProcessRequirement.FallbackValidationCategory",
+                           FallbackValidationCategoryOfCurrentProcess(),
+                           ValidationCategory::DeveloperId);
+
+  std::optional<ProcessRequirement> requirement;
+  {
+    ScopedUmaHistogramTimer timer(
+        "Mac.ProcessRequirement.Timing.BuildSameIdentityRequirement");
+    requirement =
+        Builder().SignedWithSameIdentity().CheckDynamicValidityOnly().Build();
+  }
+
+  if (requirement) {
+    ScopedUmaHistogramTimer timer(
+        "Mac.ProcessRequirement.Timing.ValidateSameIdentity");
+    bool result = requirement->ValidateProcess(
+        AuditTokenForCurrentProcess(), OuterBundleCachedInfoPlistData());
+    base::UmaHistogramBoolean("Mac.ProcessRequirement.CurrentProcessValid",
+                              result);
+  }
+}
+
 }  // namespace base::mac
diff --git a/base/mac/process_requirement.h b/base/mac/process_requirement.h
index ae37e06..753db3d 100644
--- a/base/mac/process_requirement.h
+++ b/base/mac/process_requirement.h
@@ -138,6 +138,10 @@
   // application on disk.
   bool ShouldCheckDynamicValidityOnly() const { return dynamic_validity_only_; }
 
+  // Gather metrics to validate the reliability of ProcessRequirement.
+  // Work is performed asynchronously on a background thread.
+  static void MaybeGatherMetrics();
+
   static ProcessRequirement AlwaysMatchesForTesting();
   static ProcessRequirement NeverMatchesForTesting();
 
@@ -167,6 +171,9 @@
   // This will be false for unsigned code and true for all signed code.
   bool RequiresSignatureValidation() const;
 
+  // Do the work of gathering metrics. Called on a background thread.
+  static void GatherMetrics();
+
   enum ForTesting {
     AlwaysMatches,
     NeverMatches,
diff --git a/build/android/gyp/compile_java.py b/build/android/gyp/compile_java.py
index 36baf6f3..ef79a57 100755
--- a/build/android/gyp/compile_java.py
+++ b/build/android/gyp/compile_java.py
@@ -56,27 +56,22 @@
     'DoNotClaimAnnotations',
     'JavaUtilDate',
     'IdentityHashMapUsage',
-    'UnnecessaryMethodReference',
     'LongFloatConversion',
     'CharacterGetNumericValue',
     'ErroneousThreadPoolConstructorChecker',
     'StaticMockMember',
     'MissingSuperCall',
     'ToStringReturnsNull',
+    # Triggers in tests where this is useful to do.
+    'StaticAssignmentOfThrowable',
     # If possible, this should be automatically fixed if turned on:
     'MalformedInlineTag',
-    # TODO(crbug.com/41384359): Follow steps in bug
-    'DoubleBraceInitialization',
     # TODO(crbug.com/41384349): Follow steps in bug.
     'CatchAndPrintStackTrace',
     # TODO(crbug.com/41364336): Follow steps in bug.
     'SynchronizeOnNonFinalField',
     # TODO(crbug.com/41364806): Follow steps in bug.
     'TypeParameterUnusedInFormals',
-    # TODO(crbug.com/41365724): Follow steps in bug.
-    'CatchFail',
-    # TODO(crbug.com/41365725): Follow steps in bug.
-    'JUnitAmbiguousTestClass',
     # Android platform default is always UTF-8.
     # https://developer.android.com/reference/java/nio/charset/Charset.html#defaultCharset()
     'DefaultCharset',
@@ -84,8 +79,6 @@
     'UnrecognisedJavadocTag',
     # Low priority since the alternatives still work.
     'JdkObsolete',
-    # We don't use that many lambdas.
-    'FunctionalInterfaceClash',
     # There are lots of times when we just want to post a task.
     'FutureReturnValueIgnored',
     # Just false positives in our code.
@@ -109,19 +102,7 @@
     # Low priority to fix.
     'HidingField',
     # Low priority.
-    'IntLongMath',
-    # Low priority.
-    'BadComparable',
-    # Low priority.
     'EqualsHashCode',
-    # Nice to fix but low priority.
-    'TypeParameterShadowing',
-    # Good to have immutable enums, also low priority.
-    'ImmutableEnumChecker',
-    # False positives for testing.
-    'InputStreamSlowMultibyteRead',
-    # Nice to have better primitives.
-    'BoxedPrimitiveConstructor',
     # Not necessary for tests.
     'OverrideThrowableToString',
     # Nice to have better type safety.
@@ -141,29 +122,13 @@
     'EqualsGetClass',
     # A lot of false-positives from CharSequence.equals().
     'UndefinedEquals',
-    # Nice to have.
-    'ExtendingJUnitAssert',
-    # Nice to have.
-    'SystemExitOutsideMain',
-    # Nice to have.
-    'TypeParameterNaming',
-    # Nice to have.
-    'UnusedException',
-    # Nice to have.
-    'UngroupedOverloads',
-    # Nice to have.
-    'FunctionalInterfaceClash',
-    # Nice to have.
-    'InconsistentOverloads',
     # Dagger generated code triggers this.
     'SameNameButDifferent',
-    # Nice to have.
+    # Does not apply to Android because it assumes no desugaring.
     'UnnecessaryLambda',
     # Nice to have.
     'UnnecessaryAnonymousClass',
     # Nice to have.
-    'LiteProtoToString',
-    # Nice to have.
     'MissingSummary',
     # Nice to have.
     'EmptyCatch',
@@ -173,8 +138,6 @@
     'UseCorrectAssertInTests',
     # Nice to have.
     'InlineFormatString',
-    # Nice to have.
-    'DefaultPackage',
     # Must be off since we are now passing in annotation processor generated
     # code as a source jar (deduplicating work with turbine).
     'RefersToDaggerCodegen',
@@ -189,39 +152,13 @@
     # A lot of existing violations. e.g. Should return List and not ArrayList
     'NonApiType',
     # Nice to have.
-    'Finalize',
-    # Nice to have.
-    'NotJavadoc',
-    # Nice to have.
     'DirectInvocationOnMock',
     # Nice to have.
     'StringCharset',
     # Nice to have.
-    'JUnitIncompatibleType',
-    # Nice to have.
     'MockNotUsedInProduction',
     # Nice to have.
-    'ImpossibleNullComparison',
-    # Nice to have.
-    'UnusedTypeParameter',
-    # Nice to have.
-    'EnumOrdinal',
-    # Nice to have.
-    'NullableOptional',
-    # Nice to have.
-    'SelfAssertion',
-    # Nice to have.
     'StringCaseLocaleUsage',
-    # Nice to have.
-    'JUnit4TestNotRun',
-    # Nice to have.
-    'StaticAssignmentOfThrowable',
-    # Nice to have.
-    'SuperCallToObjectMethod',
-    # Nice to have.
-    'ComparisonOutOfRange',
-    # Nice to have
-    'AddressSelection',
 ]
 
 # Full list of checks: https://errorprone.info/bugpatterns
diff --git a/build/install-build-deps.py b/build/install-build-deps.py
index a1e123c3..448b0f9 100755
--- a/build/install-build-deps.py
+++ b/build/install-build-deps.py
@@ -256,7 +256,6 @@
       "pkgconf",
       "rpm",
       "ruby",
-      "subversion",
       "uuid-dev",
       "wdiff",
       "x11-utils",
diff --git a/cc/paint/oop_pixeltest.cc b/cc/paint/oop_pixeltest.cc
index 926691a..c917280 100644
--- a/cc/paint/oop_pixeltest.cc
+++ b/cc/paint/oop_pixeltest.cc
@@ -1070,7 +1070,14 @@
                comparator);
 }
 
-TEST_F(OopPixelTest, DrawImageReinterpretedAsSRGB) {
+#if BUILDFLAG(IS_FUCHSIA) && defined(ARCH_CPU_ARM64)
+// SwiftShader crashes when running this test on ARM64 on Fuchsia,
+// see b/369849405.
+#define MAYBE_DrawImageReinterpretedAsSRGB DISABLED_DrawImageReinterpretedAsSRGB
+#else
+#define MAYBE_DrawImageReinterpretedAsSRGB DrawImageReinterpretedAsSRGB
+#endif
+TEST_F(OopPixelTest, MAYBE_DrawImageReinterpretedAsSRGB) {
   constexpr gfx::Rect rect(100, 100);
 
   auto image_color_space = gfx::ColorSpace::CreateHDR10().ToSkColorSpace();
diff --git a/cc/slim/layer_tree_impl.cc b/cc/slim/layer_tree_impl.cc
index c97999e..ce0279a 100644
--- a/cc/slim/layer_tree_impl.cc
+++ b/cc/slim/layer_tree_impl.cc
@@ -627,6 +627,15 @@
     // A layer can't have a different offset tag than it's ancestor.
     CHECK(!data.offset_tag);
 
+    // If a mask filter from a parent layer that applies to tagged `layer` then
+    // the mask filter bounds shouldn't move based on offset. Currently viz
+    // assumes that mask bounds should move so don't allow this case. Allowing
+    // this would require plumbing a bool to viz that indicates if
+    // `SharedQuadState::mask_filter_info` should be translated, see
+    // crbug.com/361804880 for details
+    CHECK(!data.mask_filter_info_in_target.HasRoundedCorners() &&
+          !data.mask_filter_info_in_target.HasGradientMask());
+
     offset_tag_reset.emplace(&data.offset_tag, layer.offset_tag());
 
     // If `layer` has an offset tag then the position `layer` will be drawn at
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinator.java
index 650f35f..60438c9 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinator.java
@@ -51,6 +51,7 @@
 import org.chromium.chrome.browser.tasks.tab_management.TabProperties.UiType;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.undo_tab_close_snackbar.UndoBarController;
+import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateProvider;
 import org.chromium.components.browser_ui.styles.SemanticColorUtils;
 import org.chromium.components.browser_ui.widget.FadingShadow;
 import org.chromium.components.browser_ui.widget.FadingShadowView;
@@ -248,6 +249,7 @@
     private final @NonNull ViewGroup mDialogView;
     private final @NonNull ViewGroup mTabSwitcherView;
     private final @NonNull FadingShadowView mShadowView;
+    private final @Nullable DesktopWindowStateProvider mDesktopWindowStateProvider;
 
     private TabListRecyclerView mDialogRecyclerView;
     private WeakReference<TabListRecyclerView> mTabSwitcherRecyclerView;
@@ -271,6 +273,7 @@
      * @param backPressManager Manages the different back press handlers throughout the app.
      * @param tabArchiveSettings The settings manager for tab archive.
      * @param modalDialogManager Used for managing the modal dialogs.
+     * @param desktopWindowStateProvider Provider to get desktop window and app header state.
      */
     public ArchivedTabsDialogCoordinator(
             @NonNull Context context,
@@ -284,7 +287,8 @@
             @NonNull TabCreator regularTabCreator,
             @NonNull BackPressManager backPressManager,
             @NonNull TabArchiveSettings tabArchiveSettings,
-            @NonNull ModalDialogManager modalDialogManager) {
+            @NonNull ModalDialogManager modalDialogManager,
+            @Nullable DesktopWindowStateProvider desktopWindowStateProvider) {
         mContext = context;
         mBrowserControlsStateProvider = browserControlsStateProvider;
         mTabContentManager = tabContentManager;
@@ -295,6 +299,7 @@
         mBackPressManager = backPressManager;
         mTabArchiveSettings = tabArchiveSettings;
         mModalDialogManager = modalDialogManager;
+        mDesktopWindowStateProvider = desktopWindowStateProvider;
 
         mArchivedTabModelOrchestrator = archivedTabModelOrchestrator;
         mArchivedTabModel =
@@ -530,7 +535,8 @@
                         /* bottomSheetController= */ null,
                         TabProperties.TabActionState.CLOSABLE,
                         mGridCardOnCLickListenerProvider,
-                        mModalDialogManager);
+                        mModalDialogManager,
+                        mDesktopWindowStateProvider);
     }
 
     @VisibleForTesting
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageService.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageService.java
index 81a5c96..9e2fe69f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageService.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageService.java
@@ -15,6 +15,7 @@
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.Callback;
@@ -32,11 +33,11 @@
 import org.chromium.chrome.browser.tabmodel.TabCreator;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tasks.tab_management.MessageCardViewProperties.MessageCardScope;
-import org.chromium.chrome.browser.tasks.tab_management.MessageService.MessageType;
 import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
 import org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.ModelType;
 import org.chromium.chrome.browser.tasks.tab_management.TabSwitcherMessageManager.MessageUpdateObserver;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
+import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateProvider;
 import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter;
 import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightParams;
 import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightShape;
@@ -137,6 +138,7 @@
     private final @NonNull Tracker mTracker;
     private final @NonNull Runnable mAppendMessageRunnable;
     private final @NonNull Supplier<TabListCoordinator> mTabListCoordinatorSupplier;
+    private final @Nullable DesktopWindowStateProvider mDesktopWindowStateProvider;
 
     private TabArchiveSettings mTabArchiveSettings;
     private ArchivedTabsDialogCoordinator mArchivedTabsDialogCoordinator;
@@ -161,7 +163,8 @@
             @NonNull ModalDialogManager modalDialogManager,
             @NonNull Tracker tracker,
             @NonNull Runnable appendMessageRunnable,
-            @NonNull Supplier<TabListCoordinator> tabListCoordinatorSupplier) {
+            @NonNull Supplier<TabListCoordinator> tabListCoordinatorSupplier,
+            @Nullable DesktopWindowStateProvider desktopWindowStateProvider) {
         super(MessageType.ARCHIVED_TABS_MESSAGE);
         mContext = context;
         mArchivedTabModelOrchestrator = archivedTabModelOrchestrator;
@@ -176,6 +179,7 @@
         mTracker = tracker;
         mAppendMessageRunnable = appendMessageRunnable;
         mTabListCoordinatorSupplier = tabListCoordinatorSupplier;
+        mDesktopWindowStateProvider = desktopWindowStateProvider;
         // Capture this value immediately before it expires when the IPH is dismissed, which will
         // happen regardless of user behavior. The TabArchiveSettings tracks whether the main IPH
         // was followed. When that's true, the archived tabs message should be highlighted as part
@@ -307,7 +311,8 @@
                         mRegularTabCreator,
                         mBackPressManager,
                         mTabArchiveSettings,
-                        mModalDialogManager);
+                        mModalDialogManager,
+                        mDesktopWindowStateProvider);
     }
 
     private void updateModelProperties() {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
index 7e975cc..261e02a 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
@@ -306,7 +306,9 @@
                             mBottomSheetController,
                             TabProperties.TabActionState.SELECTABLE,
                             /* gridCardOnClickListenerProvider= */ null,
-                            mModalDialogManager);
+                            mModalDialogManager,
+                            // Parent container handles desktop window state.
+                            /* desktopWindowStateProvider= */ null);
         }
 
         return mTabListEditorCoordinator.getController();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java
index b394b868..10f72ba 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java
@@ -35,6 +35,7 @@
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateProvider;
 import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
 import org.chromium.ui.modaldialog.ModalDialogManager;
@@ -257,6 +258,7 @@
      * @param bottomSheetController Used to display bottom sheets.
      * @param initialTabActionState The initial TabActionState to use.
      * @param modalDialogManager Used for managing the modal dialogs.
+     * @param desktopWindowStateProvider Provider to get desktop window and app header state.
      */
     public TabListEditorCoordinator(
             Context context,
@@ -271,9 +273,9 @@
             SnackbarManager snackbarManager,
             BottomSheetController bottomSheetController,
             @TabActionState int initialTabActionState,
-            @Nullable
-                    TabListMediator.GridCardOnClickListenerProvider gridCardOnClickListenerProvider,
-            @NonNull ModalDialogManager modalDialogManager) {
+            @Nullable GridCardOnClickListenerProvider gridCardOnClickListenerProvider,
+            @NonNull ModalDialogManager modalDialogManager,
+            @Nullable DesktopWindowStateProvider desktopWindowStateProvider) {
         try (TraceEvent e = TraceEvent.scoped("TabListEditorCoordinator.constructor")) {
             mContext = context;
             mRootView = rootView;
@@ -309,7 +311,8 @@
                             snackbarManager,
                             bottomSheetController,
                             mTabListEditorLayout,
-                            mTabActionState);
+                            mTabActionState,
+                            desktopWindowStateProvider);
             mTabListEditorMediator.setNavigationProvider(
                     new TabListEditorNavigationProvider(mContext, mTabListEditorController));
         }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorLayoutBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorLayoutBinder.java
index e13813d0..58df1c7 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorLayoutBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorLayoutBinder.java
@@ -4,6 +4,9 @@
 
 package org.chromium.chrome.browser.tasks.tab_management;
 
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -45,6 +48,11 @@
                             model.get(TabListEditorProperties.RELATED_TAB_COUNT_PROVIDER));
         } else if (TabListEditorProperties.TOOLBAR_TITLE == propertyKey) {
             view.getToolbar().setTitle(model.get(TabListEditorProperties.TOOLBAR_TITLE));
+        } else if (TabListEditorProperties.TOP_MARGIN == propertyKey) {
+            ViewGroup.MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
+            layoutParams.topMargin = model.get(TabListEditorProperties.TOP_MARGIN);
+            // Calling setLayoutParams to requestLayout() for margin to take effect.
+            view.setLayoutParams(layoutParams);
         }
     }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorManager.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorManager.java
index e95cddcf..610e211 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorManager.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorManager.java
@@ -26,6 +26,7 @@
 import org.chromium.chrome.browser.tinker_tank.TinkerTankDelegate;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateProvider;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 
 import java.util.ArrayList;
@@ -49,6 +50,7 @@
     private final @NonNull ObservableSupplierImpl<TabListEditorController> mControllerSupplier =
             new ObservableSupplierImpl<>();
     private final TabGroupCreationDialogManager mTabGroupCreationDialogManager;
+    private final @Nullable DesktopWindowStateProvider mDesktopWindowStateProvider;
 
     private @Nullable TabListEditorCoordinator mTabListEditorCoordinator;
     private @Nullable List<TabListEditorAction> mTabListEditorActions;
@@ -76,7 +78,8 @@
             @NonNull TabListCoordinator tabListCoordinator,
             BottomSheetController bottomSheetController,
             @TabListMode int mode,
-            @Nullable Runnable onTabGroupCreation) {
+            @Nullable Runnable onTabGroupCreation,
+            @Nullable DesktopWindowStateProvider desktopWindowStateProvider) {
         mActivity = activity;
         mModalDialogManager = modalDialogManager;
         mCoordinatorView = coordinatorView;
@@ -89,6 +92,7 @@
         mMode = mode;
         mTabGroupCreationDialogManager =
                 new TabGroupCreationDialogManager(activity, modalDialogManager, onTabGroupCreation);
+        mDesktopWindowStateProvider = desktopWindowStateProvider;
 
         // The snackbarManager used by mTabListEditorCoordinator. The rootView is the default
         // default parent view of the snackbar. When shown this will be re-parented inside the
@@ -130,7 +134,8 @@
                             mBottomSheetController,
                             TabProperties.TabActionState.SELECTABLE,
                             /* gridCardOnClickListenerProvider= */ null,
-                            mModalDialogManager);
+                            mModalDialogManager,
+                            mDesktopWindowStateProvider);
             mControllerSupplier.set(mTabListEditorCoordinator.getController());
         }
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediator.java
index 26e9d08..1851445 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediator.java
@@ -29,8 +29,10 @@
 import org.chromium.chrome.browser.tasks.tab_management.TabUiMetricsHelper.TabListEditorExitMetricGroups;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.desktop_windowing.AppHeaderState;
+import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateProvider;
+import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateProvider.AppHeaderObserver;
 import org.chromium.components.browser_ui.styles.ChromeColors;
-import org.chromium.components.browser_ui.widget.gesture.BackPressHandler.BackPressResult;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
 import org.chromium.ui.modelutil.ListModelChangeProcessor;
 import org.chromium.ui.modelutil.PropertyKey;
@@ -48,7 +50,8 @@
  */
 class TabListEditorMediator
         implements TabListEditorCoordinator.TabListEditorController,
-                TabListEditorAction.ActionDelegate {
+                TabListEditorAction.ActionDelegate,
+                AppHeaderObserver {
     private final Context mContext;
     private final @NonNull ObservableSupplier<TabModelFilter> mCurrentTabModelFilterSupplier;
     private final @NonNull ValueChangedCallback<TabModelFilter> mOnTabModelFilterChanged =
@@ -61,6 +64,7 @@
             new ObservableSupplierImpl<>();
     private final List<Tab> mVisibleTabs = new ArrayList<>();
     private final TabListEditorLayout mTabListEditorLayout;
+    private final @Nullable DesktopWindowStateProvider mDesktopWindowStateProvider;
 
     private @Nullable TabListCoordinator mTabListCoordinator;
     private @Nullable TabListEditorCoordinator.ResetHandler mResetHandler;
@@ -92,7 +96,8 @@
             SnackbarManager snackbarManager,
             BottomSheetController bottomSheetController,
             TabListEditorLayout tabListEditorLayout,
-            @TabActionState int initialTabActionState) {
+            @TabActionState int initialTabActionState,
+            @Nullable DesktopWindowStateProvider desktopWindowStateProvider) {
         mContext = context;
         mCurrentTabModelFilterSupplier = currentTabModelFilterSupplier;
         mModel = model;
@@ -102,6 +107,7 @@
         mBottomSheetController = bottomSheetController;
         mTabListEditorLayout = tabListEditorLayout;
         mTabActionState = initialTabActionState;
+        mDesktopWindowStateProvider = desktopWindowStateProvider;
 
         mTabModelObserver =
                 new TabModelObserver() {
@@ -153,6 +159,12 @@
                         mBackPressChangedSupplier.set(isEditorVisible());
                     }
                 });
+        if (mDesktopWindowStateProvider != null) {
+            mDesktopWindowStateProvider.addObserver(this);
+            if (mDesktopWindowStateProvider.getAppHeaderState() != null) {
+                onAppHeaderStateChanged(mDesktopWindowStateProvider.getAppHeaderState());
+            }
+        }
     }
 
     private boolean isEditorVisible() {
@@ -375,6 +387,12 @@
         return mBottomSheetController;
     }
 
+    /** AppHeaderObserver implementation */
+    @Override
+    public void onAppHeaderStateChanged(AppHeaderState newState) {
+        mModel.set(TabListEditorProperties.TOP_MARGIN, newState.getAppHeaderHeight());
+    }
+
     /** Destroy any members that needs clean up. */
     public void destroy() {
         removeTabModelFilterObserver(mCurrentTabModelFilterSupplier.get());
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediatorUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediatorUnitTest.java
new file mode 100644
index 0000000..e83d440c
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediatorUnitTest.java
@@ -0,0 +1,75 @@
+// 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.tasks.tab_management;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.tabmodel.TabModelFilter;
+import org.chromium.chrome.browser.tasks.tab_management.TabProperties.TabActionState;
+import org.chromium.components.browser_ui.desktop_windowing.AppHeaderState;
+import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateProvider;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/** Unit tests for {@link TabListEditorMediator}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public final class TabListEditorMediatorUnitTest {
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private Context mContext;
+    @Mock private ObservableSupplier<TabModelFilter> mTabModelFilterSupplier;
+    @Mock private DesktopWindowStateProvider mDesktopWindowStateProvider;
+
+    private PropertyModel mModel;
+    private TabListEditorMediator mMediator;
+
+    @Before
+    public void setUp() {
+        mModel = new PropertyModel.Builder(TabListEditorProperties.ALL_KEYS).build();
+        mMediator =
+                new TabListEditorMediator(
+                        mContext,
+                        mTabModelFilterSupplier,
+                        mModel,
+                        /* selectionDelegate= */ null,
+                        /* actionOnRelatedTabs= */ false,
+                        /* snackbarManager= */ null,
+                        /* bottomSheetController= */ null,
+                        /* tabListEditorLayout= */ null,
+                        TabActionState.SELECTABLE,
+                        mDesktopWindowStateProvider);
+    }
+
+    @After
+    public void tearDown() {
+        mMediator.destroy();
+    }
+
+    @Test
+    @SmallTest
+    public void testTopMarginOnAppHeaderStateChange() {
+        AppHeaderState state = mock(AppHeaderState.class);
+        when(state.getAppHeaderHeight()).thenReturn(10);
+
+        mMediator.onAppHeaderStateChanged(state);
+
+        assertEquals(10, mModel.get(TabListEditorProperties.TOP_MARGIN));
+    }
+}
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorProperties.java
index f6ef86b..845ddef 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorProperties.java
@@ -37,6 +37,9 @@
     public static final PropertyModel.WritableObjectPropertyKey<String> TOOLBAR_TITLE =
             new PropertyModel.WritableObjectPropertyKey<>();
 
+    public static final PropertyModel.WritableIntPropertyKey TOP_MARGIN =
+            new PropertyModel.WritableIntPropertyKey();
+
     public static final PropertyKey[] ALL_KEYS =
             new PropertyKey[] {
                 IS_VISIBLE,
@@ -46,6 +49,7 @@
                 TOOLBAR_TEXT_TINT,
                 TOOLBAR_BUTTON_TINT,
                 RELATED_TAB_COUNT_PROVIDER,
-                TOOLBAR_TITLE
+                TOOLBAR_TITLE,
+                TOP_MARGIN
             };
 }
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 732fa16..7fe9f8c 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
@@ -46,6 +46,7 @@
 import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.tab_ui.R;
+import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateProvider;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modelutil.LayoutViewBuilder;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -144,6 +145,7 @@
     private final @NonNull ViewGroup mRootView;
     private final @NonNull TabCreator mRegularTabCreator;
     private final @NonNull BackPressManager mBackPressManager;
+    private final @Nullable DesktopWindowStateProvider mDesktopWindowStateProvider;
 
     private @Nullable Profile mProfile;
     private @Nullable PriceMessageService mPriceMessageService;
@@ -164,6 +166,7 @@
      * @param rootView The root {@link ViewGroup} to attach dialogs to.
      * @param regularTabCreator Manages the creation of regular tabs.
      * @param backPressManager Manages the different back press handlers in the app.
+     * @param desktopWindowStateProvider Provider to get desktop window and app header state.
      */
     public TabSwitcherMessageManager(
             @NonNull Context context,
@@ -177,7 +180,8 @@
             @TabListMode int tabListMode,
             @NonNull ViewGroup rootView,
             @NonNull TabCreator regularTabCreator,
-            @NonNull BackPressManager backPressManager) {
+            @NonNull BackPressManager backPressManager,
+            @Nullable DesktopWindowStateProvider desktopWindowStateProvider) {
         mContext = context;
         mLifecylceDispatcher = lifecycleDispatcher;
         mCurrentTabModelFilterSupplier = currentTabModelFilterSupplier;
@@ -190,6 +194,7 @@
         mRootView = rootView;
         mRegularTabCreator = regularTabCreator;
         mBackPressManager = backPressManager;
+        mDesktopWindowStateProvider = desktopWindowStateProvider;
 
         mMessageCardProviderCoordinator =
                 new MessageCardProviderCoordinator(
@@ -298,7 +303,8 @@
                             () ->
                                     appendNextMessage(
                                             MessageService.MessageType.ARCHIVED_TABS_MESSAGE),
-                            mTabListCoordinatorSupplier);
+                            mTabListCoordinatorSupplier,
+                            mDesktopWindowStateProvider);
             addObserver(mArchivedTabsMessageService);
             mMessageCardProviderCoordinator.subscribeMessageService(mArchivedTabsMessageService);
         }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManagerUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManagerUnitTest.java
index 312298a..e83a3d55 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManagerUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManagerUnitTest.java
@@ -140,7 +140,8 @@
                         TabListMode.GRID,
                         mRootView,
                         mRegularTabCreator,
-                        mBackPressManager);
+                        mBackPressManager,
+                        /* desktopWindowStateProvider= */ null);
         mMessageManager.registerMessages(mTabListCoordinator);
         mMessageManager.bind(
                 mTabListCoordinator,
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 e4245e5..c58c7b1 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
@@ -367,7 +367,8 @@
                             tabListCoordinator,
                             bottomSheetController,
                             mode,
-                            onTabGroupCreation);
+                            onTabGroupCreation,
+                            desktopWindowStateProvider);
             mTabListEditorManager = tabListEditorManager;
             mMediator.setTabListEditorControllerSupplier(
                     mTabListEditorManager.getControllerSupplier());
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 bb38b62..8f89fdc1 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
@@ -257,7 +257,8 @@
                             mMode,
                             mActivity.findViewById(R.id.coordinator),
                             mTabCreatorManager.getTabCreator(/* incognito= */ false),
-                            mBackPressManager);
+                            mBackPressManager,
+                            mDesktopWindowStateProvider);
             if (mLifecycleDispatcher.isNativeInitializationFinished()) {
                 mMessageManager.initWithNative(
                         mProfileProviderSupplier.get().getOriginalProfile(), getTabListMode());
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/ClosableTabListEditorTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/ClosableTabListEditorTest.java
index 322ab030..e220f86b 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/ClosableTabListEditorTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/ClosableTabListEditorTest.java
@@ -98,7 +98,8 @@
                                     /* bottomSheetController= */ null,
                                     TabProperties.TabActionState.CLOSABLE,
                                     /* gridCardOnClickListenerProvider= */ null,
-                                    mModalDialogManager);
+                                    mModalDialogManager,
+                                    /* desktopWindowStateProvider= */ null);
 
                     mTabListEditorController = mTabListEditorCoordinator.getController();
                     mTabListEditorLayout =
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/SelectableTabListEditorTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/SelectableTabListEditorTest.java
index e94575b8..be1bd9d 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/SelectableTabListEditorTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/SelectableTabListEditorTest.java
@@ -32,15 +32,19 @@
 import static org.chromium.ui.test.util.MockitoHelper.doCallback;
 
 import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.annotation.IdRes;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.test.espresso.Espresso;
 import androidx.test.espresso.intent.Intents;
 import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.After;
@@ -64,12 +68,14 @@
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Features.DisableFeatures;
+import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.base.test.util.RequiresRestart;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.app.bookmarks.BookmarkEditActivity;
 import org.chromium.chrome.browser.bookmarks.BookmarkModel;
 import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.tab.Tab;
@@ -84,6 +90,7 @@
 import org.chromium.chrome.browser.tasks.tab_management.TabListEditorAction.IconPosition;
 import org.chromium.chrome.browser.tasks.tab_management.TabListEditorAction.ShowMode;
 import org.chromium.chrome.browser.tasks.tab_management.TabProperties.TabActionState;
+import org.chromium.chrome.browser.ui.desktop_windowing.AppHeaderCoordinator;
 import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -93,6 +100,7 @@
 import org.chromium.chrome.test.util.BookmarkTestUtil;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.components.browser_ui.desktop_windowing.AppHeaderState;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.modaldialog.ModalDialogManager;
@@ -155,6 +163,7 @@
     private SnackbarManager mSnackbarManager;
     private BookmarkModel mBookmarkModel;
     private TabGroupCreationDialogManager mCreationDialogManager;
+    private AppHeaderCoordinator mAppHeaderStateProvider;
 
     @Before
     public void setUp() throws Exception {
@@ -191,6 +200,12 @@
                             mTabModelSelector
                                     .getTabModelFilterProvider()
                                     .getCurrentTabModelFilterSupplier();
+                    mAppHeaderStateProvider =
+                            (AppHeaderCoordinator)
+                                    sActivityTestRule
+                                            .getActivity()
+                                            .getRootUiCoordinatorForTesting()
+                                            .getDesktopWindowStateProvider();
                     mTabListEditorCoordinator =
                             new TabListEditorCoordinator(
                                     cta,
@@ -206,7 +221,8 @@
                                     /* bottomSheetController= */ null,
                                     TabProperties.TabActionState.SELECTABLE,
                                     /* gridCardOnClickListenerProvider= */ null,
-                                    mModalDialogManager);
+                                    mModalDialogManager,
+                                    mAppHeaderStateProvider);
 
                     mTabListEditorController = mTabListEditorCoordinator.getController();
                     mTabListEditorLayout =
@@ -365,6 +381,47 @@
     }
 
     @Test
+    @RequiresApi(Build.VERSION_CODES.R)
+    @EnableFeatures(ChromeFeatureList.TAB_STRIP_LAYOUT_OPTIMIZATION)
+    @Restriction(DeviceFormFactor.TABLET)
+    @Feature("DesktopWindow")
+    @SmallTest
+    public void testMarginWithAppHeaders() {
+        // Height to apply as top margin.
+        int appHeaderHeight =
+                sActivityTestRule
+                        .getActivity()
+                        .getResources()
+                        .getDimensionPixelSize(R.dimen.tab_strip_height);
+        Rect windowRect = new Rect();
+        sActivityTestRule.getActivity().getWindow().getDecorView().getGlobalVisibleRect(windowRect);
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    // Trigger desktop window - set app headers
+                    Rect widestUnoccludedRect =
+                            new Rect(windowRect.left, 0, windowRect.right, appHeaderHeight);
+                    var state = new AppHeaderState(windowRect, widestUnoccludedRect, true);
+                    mAppHeaderStateProvider.setStateForTesting(true, state);
+                });
+
+        prepareBlankTab(2, false);
+        List<Tab> tabs = getTabsInCurrentTabModel();
+        showSelectionEditor(tabs, null);
+
+        mRobot.resultRobot.verifyTabListEditorHasTopMargin(appHeaderHeight);
+
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    // Exit desktop window.
+                    var state = new AppHeaderState(windowRect, new Rect(), false);
+                    mAppHeaderStateProvider.setStateForTesting(false, state);
+                });
+
+        // Verify margin is reset.
+        mRobot.resultRobot.verifyTabListEditorHasTopMargin(0);
+    }
+
+    @Test
     @MediumTest
     public void testToggleItem() {
         prepareBlankTab(2, false);
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorTestingRobot.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorTestingRobot.java
index 4eeb21b..8ca3b736 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorTestingRobot.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorTestingRobot.java
@@ -29,6 +29,7 @@
 import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
 
 import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
 
 import androidx.annotation.IdRes;
 import androidx.recyclerview.widget.RecyclerView;
@@ -382,5 +383,17 @@
                     .check(matches(isDisplayed()));
             return this;
         }
+
+        public Result verifyTabListEditorHasTopMargin(int topMargin) {
+            onView(withId(R.id.selectable_list))
+                    .check(
+                            (v, noMatchException) -> {
+                                if (noMatchException != null) throw noMatchException;
+                                Assert.assertEquals(
+                                        topMargin,
+                                        ((MarginLayoutParams) v.getLayoutParams()).topMargin);
+                            });
+            return this;
+        }
     }
 }
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorUnitTest.java
index 7c4e1b8c..c06a776 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorUnitTest.java
@@ -126,7 +126,8 @@
                         mRegularTabCreator,
                         mBackPressManager,
                         mTabArchiveSettings,
-                        mModalDialogManager);
+                        mModalDialogManager,
+                        /* desktopWindowStateProvider= */ null);
         mCoordinator.setTabListEditorCoordinatorForTesting(mTabListEditorCoordinator);
         recyclerView = new TabListRecyclerView(mContext, null);
         recyclerView.setId(R.id.tab_list_recycler_view);
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageServiceUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageServiceUnitTest.java
index b4f00df4..4a80545 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageServiceUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageServiceUnitTest.java
@@ -108,7 +108,8 @@
                         mModalDialogManager,
                         mTracker,
                         mAppendMessageRunnable,
-                        mTabListCoordinatorSupplier);
+                        mTabListCoordinatorSupplier,
+                        /* desktopWindowStateProvider= */ null);
         mArchivedTabsMessageService.setArchivedTabsDialogCoordiantorForTesting(
                 mArchivedTabsDialogCoordinator);
         mArchivedTabsMessageService.addObserver(mMessageObserver);
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni
index d0415689..bc79eed 100644
--- a/chrome/android/features/tab_ui/tab_management_java_sources.gni
+++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
@@ -232,6 +232,7 @@
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupTimeAgoResolverUnitTest.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiOneshotSupplierUnitTest.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPaneUnitTest.java",
+  "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediatorUnitTest.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListGroupMenuCoordinatorUnitTest.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListItemAnimatorUnitTest.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListModelUnitTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
index b5c2234..2fb61e1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
@@ -65,6 +65,7 @@
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.OmniboxTestUtils;
 import org.chromium.components.embedder_support.util.UrlConstants;
+import org.chromium.components.omnibox.OmniboxFeatures;
 import org.chromium.components.search_engines.TemplateUrl;
 import org.chromium.components.search_engines.TemplateUrlService;
 import org.chromium.content_public.common.ContentSwitches;
@@ -612,6 +613,33 @@
     })
     @Restriction(DeviceFormFactor.TABLET)
     public void testFocusLogic_buttonVisibilityTablet() {
+        testFocusLogic_buttonVisibilityTablet(
+                /* expectRetainOmniboxOnFocus= */ OmniboxFeatures.shouldRetainOmniboxOnFocus());
+    }
+
+    @Test
+    @MediumTest
+    @CommandLineFlags.Add({
+        "disable-features=" + ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2
+    })
+    @Restriction(DeviceFormFactor.TABLET)
+    public void testFocusLogic_buttonVisibilityTabletWithRetainOmniboxOnFocusDisabled() {
+        OmniboxFeatures.setShouldRetainOmniboxOnFocusForTesting(Boolean.FALSE);
+        testFocusLogic_buttonVisibilityTablet(/* expectRetainOmniboxOnFocus= */ false);
+    }
+
+    @Test
+    @MediumTest
+    @CommandLineFlags.Add({
+        "disable-features=" + ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2
+    })
+    @Restriction(DeviceFormFactor.TABLET)
+    public void testFocusLogic_buttonVisibilityTabletWithRetainOmniboxOnFocusEnabled() {
+        OmniboxFeatures.setShouldRetainOmniboxOnFocusForTesting(Boolean.TRUE);
+        testFocusLogic_buttonVisibilityTablet(/* expectRetainOmniboxOnFocus= */ true);
+    }
+
+    private void testFocusLogic_buttonVisibilityTablet(boolean expectRetainOmniboxOnFocus) {
         startActivityNormally();
         doReturn(true).when(mVoiceRecognitionHandler).isVoiceSearchEnabled();
         String url =
@@ -637,10 +665,17 @@
                     mUrlBar.requestFocus();
                 });
 
-        onView(withId(R.id.mic_button))
-                .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
-        onView(withId(R.id.delete_button))
-                .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
+        if (expectRetainOmniboxOnFocus) {
+            onView(withId(R.id.mic_button))
+                    .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
+            onView(withId(R.id.delete_button))
+                    .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
+        } else {
+            onView(withId(R.id.mic_button))
+                    .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
+            onView(withId(R.id.delete_button))
+                    .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
+        }
 
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/DefaultSearchEngineDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/DefaultSearchEngineDialogTest.java
index d57957c2..5d336dd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/DefaultSearchEngineDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/DefaultSearchEngineDialogTest.java
@@ -38,6 +38,7 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.UserActionTester;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
@@ -48,6 +49,7 @@
 import org.chromium.components.search_engines.FakeTemplateUrl;
 import org.chromium.components.search_engines.TemplateUrl;
 import org.chromium.components.search_engines.TemplateUrlService;
+import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.test.util.BlankUiTestActivity;
 
 import java.util.Arrays;
@@ -163,6 +165,7 @@
 
     @Test
     @LargeTest
+    @DisableIf.Device(DeviceFormFactor.TABLET)
     public void testButtonClickRunsCallback() {
         showDialog(SearchEnginePromoType.SHOW_EXISTING);
         onView(withText(R.string.search_engine_dialog_title))
@@ -177,6 +180,7 @@
 
     @Test
     @LargeTest
+    @DisableIf.Device(DeviceFormFactor.TABLET)
     public void testButtonClickDismissesDialog() {
         showDialog(SearchEnginePromoType.SHOW_EXISTING);
         onView(withText(R.string.search_engine_dialog_title))
diff --git a/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/PlaybackArgs.java b/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/PlaybackArgs.java
index 3f0b4011..7fe38a2 100644
--- a/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/PlaybackArgs.java
+++ b/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/PlaybackArgs.java
@@ -73,6 +73,35 @@
             int PEACEFUL = 12;
         }
 
+        /** Enum for Voice Display Names. */
+        @IntDef({
+            DisplayName.NONE,
+            DisplayName.RUBY,
+            DisplayName.RIVER,
+            DisplayName.FIELD,
+            DisplayName.MOSS,
+            DisplayName.CLOUD,
+            DisplayName.DALE,
+            DisplayName.LAKE,
+            DisplayName.AIR,
+            DisplayName.COAST,
+            DisplayName.CORAL,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface DisplayName {
+            int NONE = 0;
+            int RUBY = 1;
+            int RIVER = 2;
+            int FIELD = 3;
+            int MOSS = 4;
+            int CLOUD = 5;
+            int DALE = 6;
+            int LAKE = 7;
+            int AIR = 8;
+            int COAST = 9;
+            int CORAL = 10;
+        }
+
         private final String mLanguage;
         @Nullable private final String mAccentRegionCode;
         private final String mVoiceId;
@@ -80,6 +109,7 @@
 
         private final @Pitch int mPitch;
         private final @Tone int mTone;
+        private final @DisplayName int mVoiceDisplayName;
 
         // Deprecated. Remove once internal code no longer uses it.
         public PlaybackVoice(String language, String voiceId, String displayName) {
@@ -103,6 +133,23 @@
             mAccentRegionCode = accentRegionCode;
             mVoiceId = voiceId;
             mDisplayName = displayName;
+            mVoiceDisplayName = DisplayName.NONE;
+            mPitch = pitch;
+            mTone = tone;
+        }
+
+        public PlaybackVoice(
+                String language,
+                @Nullable String accentRegionCode,
+                String voiceId,
+                @DisplayName int displayName,
+                @Pitch int pitch,
+                @Tone int tone) {
+            mLanguage = language;
+            mAccentRegionCode = accentRegionCode;
+            mVoiceId = voiceId;
+            mVoiceDisplayName = displayName;
+            mDisplayName = null;
             mPitch = pitch;
             mTone = tone;
         }
@@ -142,6 +189,10 @@
             return mDisplayName;
         }
 
+        public @DisplayName int getVoiceDisplayName() {
+            return mVoiceDisplayName;
+        }
+
         public @Pitch int getPitch() {
             return mPitch;
         }
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index a826cc0..5c27971 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -390,10 +390,11 @@
     "dips/dips_navigation_flow_detector_wrapper.h",
     "dips/dips_redirect_info.cc",
     "dips/dips_redirect_info.h",
-    "dips/dips_service.cc",
     "dips/dips_service.h",
     "dips/dips_service_factory.cc",
     "dips/dips_service_factory.h",
+    "dips/dips_service_impl.cc",
+    "dips/dips_service_impl.h",
     "dips/dips_state.cc",
     "dips/dips_state.h",
     "dips/dips_storage.cc",
@@ -5195,6 +5196,7 @@
       "//chrome/browser/ui/ash/keyboard",
       "//chrome/browser/ui/ash/login",
       "//chrome/browser/ui/ash/main_extra_parts",
+      "//chrome/browser/ui/ash/management_disclosure",
       "//chrome/browser/ui/ash/media_client",
       "//chrome/browser/ui/ash/multi_user",
       "//chrome/browser/ui/ash/network",
@@ -5634,6 +5636,7 @@
       "//chrome/browser/ui/ash/in_session_auth",
       "//chrome/browser/ui/ash/keyboard",
       "//chrome/browser/ui/ash/login",
+      "//chrome/browser/ui/ash/management_disclosure",
       "//chrome/browser/ui/ash/media_client",
       "//chrome/browser/ui/ash/multi_user",
       "//chrome/browser/ui/ash/network",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 040460b..27bf296 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3988,6 +3988,8 @@
          std::size(
              kExtensionTelemetryEnterpriseReportingIntervalSeconds_300Seconds),
          nullptr}};
+constexpr char kExtensionAiDataInternalName[] =
+    "enable-extension-ai-data-collection";
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
 const FeatureEntry::FeatureParam kDiscountOnShoppyPage[] = {
@@ -4186,12 +4188,6 @@
      flag_descriptions::kExtensionsOnChromeUrlsDescription, kOsAll,
      SINGLE_VALUE_TYPE(extensions::switches::kExtensionsOnChromeURLs)},
 #endif  // ENABLE_EXTENSIONS
-    {"omnibox-drop-unrecognized-templateurl-parameters",
-     flag_descriptions::kDropUnrecognizedTemplateUrlParametersName,
-     flag_descriptions::kDropUnrecognizedTemplateUrlParametersDescription,
-     kOsAll,
-     FEATURE_VALUE_TYPE(omnibox::kDropUnrecognizedTemplateUrlParameters)},
-
 #if BUILDFLAG(IS_ANDROID)
     {"contextual-search-suppress-short-view",
      flag_descriptions::kContextualSearchSuppressShortViewName,
@@ -6188,6 +6184,11 @@
      flag_descriptions::kAndroidAppIntegrationDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kAndroidAppIntegration)},
 
+    {"android-app-integration-with-favicon",
+     flag_descriptions::kAndroidAppIntegrationWithFaviconName,
+     flag_descriptions::kAndroidAppIntegrationWithFaviconDescription,
+     kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kAndroidAppIntegrationWithFavicon)},
     {"android-bottom-toolbar", flag_descriptions::kAndroidBottomToolbarName,
      flag_descriptions::kAndroidBottomToolbarDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kAndroidBottomToolbar)},
@@ -9285,7 +9286,7 @@
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
-    {"extension-ai-data-collection",
+    {kExtensionAiDataInternalName,
      flag_descriptions::kExtensionAiDataCollectionName,
      flag_descriptions::kExtensionAiDataCollectionDescription, kOsDesktop,
      SINGLE_VALUE_TYPE(switches::kExtensionAiDataCollection)},
@@ -12068,6 +12069,16 @@
            channel != version_info::Channel::UNKNOWN;
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+  version_info::Channel chrome_channel = chrome::GetChannel();
+  // Only show extension AI data flag in non-stable channels.
+  if (!strcmp(kExtensionAiDataInternalName, entry.internal_name)) {
+    return chrome_channel != version_info::Channel::BETA &&
+           chrome_channel != version_info::Channel::DEV &&
+           chrome_channel != version_info::Channel::CANARY &&
+           chrome_channel != version_info::Channel::UNKNOWN;
+  }
+#endif
   if (flags::IsFlagExpired(storage, entry.internal_name)) {
     return true;
   }
diff --git a/chrome/browser/apps/app_shim/app_shim_manager_mac.cc b/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
index 9bb952f..6b3e15ad 100644
--- a/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
+++ b/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
@@ -216,9 +216,7 @@
   }
 
   return AppShimRegistry::Get()->VerifyCdHashForApp(
-      base::SysCFStringRefToUTF8(app_id),
-      base::make_span(CFDataGetBytePtr(cd_hash),
-                      base::checked_cast<size_t>(CFDataGetLength(cd_hash))));
+      base::SysCFStringRefToUTF8(app_id), base::apple::CFDataToSpan(cd_hash));
 }
 
 // Returns whether |app_shim_audit_token|'s code signature is trusted. Since an
diff --git a/chrome/browser/ash/app_restore/BUILD.gn b/chrome/browser/ash/app_restore/BUILD.gn
index 9fe2dd3..58ab32f 100644
--- a/chrome/browser/ash/app_restore/BUILD.gn
+++ b/chrome/browser/ash/app_restore/BUILD.gn
@@ -203,6 +203,7 @@
     "//chrome/browser/ui/ash/desks",
     "//chrome/browser/ui/ash/device_scheduled_reboot",
     "//chrome/browser/ui/ash/system_web_apps",
+    "//chrome/browser/ui/webui/ash/settings:settings",
     "//chrome/browser/web_applications",
     "//chrome/browser/web_applications:web_applications_test_support",
     "//chrome/common",
diff --git a/chrome/browser/ash/app_restore/DEPS b/chrome/browser/ash/app_restore/DEPS
index 2950f07..53fd021 100644
--- a/chrome/browser/ash/app_restore/DEPS
+++ b/chrome/browser/ash/app_restore/DEPS
@@ -48,6 +48,7 @@
   "+chrome/browser/ui/settings_window_manager_chromeos.h",
   "+chrome/browser/ui/startup",
   "+chrome/browser/ui/web_applications/test",
+  "+chrome/browser/ui/webui/ash/settings",
   "+chrome/browser/web_applications/test",
   "+chrome/browser/web_applications/web_app_id_constants.h",
   "+chrome/browser/web_applications/web_app_install_info.h",
diff --git a/chrome/browser/ash/app_restore/full_restore_service.cc b/chrome/browser/ash/app_restore/full_restore_service.cc
index 2da5418..ce524346 100644
--- a/chrome/browser/ash/app_restore/full_restore_service.cc
+++ b/chrome/browser/ash/app_restore/full_restore_service.cc
@@ -156,6 +156,9 @@
 
 bool g_restore_for_testing = true;
 
+// If true, do not show any full restore UI.
+bool g_last_session_sanitized = false;
+
 const char kRestoreForCrashNotificationId[] = "restore_for_crash_notification";
 const char kRestoreNotificationId[] = "restore_notification";
 
@@ -278,6 +281,11 @@
   }
 }
 
+// static
+void FullRestoreService::SetLastSessionSanitized() {
+  g_last_session_sanitized = true;
+}
+
 void FullRestoreService::Init(bool& show_notification) {
   // If it is the first time to migrate to the full restore release, we don't
   // have other restore data, so we don't need to consider restoration.
@@ -640,6 +648,10 @@
 void FullRestoreService::MaybeShowRestoreNotification(
     InformedRestoreContentsData::DialogType dialog_type,
     bool& show_notification) {
+  if (g_last_session_sanitized) {
+    return;
+  }
+
   if (!app_launch_handler_) {
     return;
   }
diff --git a/chrome/browser/ash/app_restore/full_restore_service.h b/chrome/browser/ash/app_restore/full_restore_service.h
index c45603d..210132a 100644
--- a/chrome/browser/ash/app_restore/full_restore_service.h
+++ b/chrome/browser/ash/app_restore/full_restore_service.h
@@ -88,6 +88,11 @@
   FullRestoreService& operator=(const FullRestoreService&) = delete;
   ~FullRestoreService() override;
 
+  // If the last session was sanitized, skip showing any full restore UI. It is
+  // a static function since the pref gets reset before a `FullRestoreService`
+  // is created.
+  static void SetLastSessionSanitized();
+
   FullRestoreAppLaunchHandler* app_launch_handler() {
     return app_launch_handler_.get();
   }
diff --git a/chrome/browser/ash/app_restore/informed_restore_browsertest.cc b/chrome/browser/ash/app_restore/informed_restore_browsertest.cc
index 6e3e8f41..5af09cb 100644
--- a/chrome/browser/ash/app_restore/informed_restore_browsertest.cc
+++ b/chrome/browser/ash/app_restore/informed_restore_browsertest.cc
@@ -30,6 +30,7 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/webui/ash/settings/pref_names.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/ash/util/ash_test_util.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -112,6 +113,9 @@
  public:
   InformedRestoreTest() {
     set_launch_browser_for_testing(nullptr);
+
+    feature_list_.InitWithFeatures(
+        {features::kForestFeature, features::kSanitize}, {});
   }
   InformedRestoreTest(const InformedRestoreTest&) = delete;
   InformedRestoreTest& operator=(const InformedRestoreTest&) = delete;
@@ -132,7 +136,7 @@
   base::HistogramTester histogram_tester_;
 
  private:
-  base::test::ScopedFeatureList feature_list_{features::kForestFeature};
+  base::test::ScopedFeatureList feature_list_;
 };
 
 // Creates 2 browser windows that will be restored in the main test.
@@ -747,4 +751,34 @@
   EXPECT_FALSE(InformedRestoreTestApi().GetOnboardingDialog());
 }
 
+IN_PROC_BROWSER_TEST_F(InformedRestoreOnboardingTest, PRE_Sanitized) {
+  // The restore pref setting is 'Ask every time' by default.
+  auto* prefs = ProfileManager::GetActiveUserProfile()->GetPrefs();
+  EXPECT_EQ(static_cast<int>(RestoreOption::kAskEveryTime),
+            prefs->GetInteger(prefs::kRestoreAppsAndPagesPrefName));
+  prefs->SetBoolean(prefs::kShowInformedRestoreOnboarding, true);
+  prefs->SetBoolean(settings::prefs::kSanitizeCompleted, true);
+
+  Profile* profile = ProfileManager::GetActiveUserProfile();
+  CreateBrowser(profile);
+  EXPECT_EQ(1u, BrowserList::GetInstance()->size());
+
+  // Immediate save to full restore file to bypass the 2.5 second throttle.
+  AppLaunchInfoSaveWaiter::Wait();
+}
+
+// Tests that when the previous session was sanitized, we do not show any
+// informed restore UI.
+IN_PROC_BROWSER_TEST_F(InformedRestoreOnboardingTest, Sanitized) {
+  auto* onboarding_dialog = InformedRestoreTestApi().GetOnboardingDialog();
+  EXPECT_FALSE(onboarding_dialog);
+
+  // The informed restore dialog is built based on the values in this data
+  // structure. Test that it is null since we aren't planning on showing the
+  // dialog if the previous session was sanitized.
+  const InformedRestoreContentsData* contents_data =
+      Shell::Get()->informed_restore_controller()->contents_data();
+  EXPECT_FALSE(contents_data);
+}
+
 }  // namespace ash::full_restore
diff --git a/chrome/browser/ash/file_manager/volume_manager.cc b/chrome/browser/ash/file_manager/volume_manager.cc
index 76758c9..a515182 100644
--- a/chrome/browser/ash/file_manager/volume_manager.cc
+++ b/chrome/browser/ash/file_manager/volume_manager.cc
@@ -28,7 +28,6 @@
 #include "chrome/browser/ash/arc/fileapi/arc_documents_provider_util.h"
 #include "chrome/browser/ash/arc/fileapi/arc_file_system_operation_runner.h"
 #include "chrome/browser/ash/arc/fileapi/arc_media_view_util.h"
-#include "chrome/browser/ash/arc/session/arc_session_manager.h"
 #include "chrome/browser/ash/crostini/crostini_manager.h"
 #include "chrome/browser/ash/drive/drive_integration_service.h"
 #include "chrome/browser/ash/file_manager/fileapi_util.h"
@@ -1719,9 +1718,8 @@
   }
   // TODO(crbug.com/40497410): We need nullptr check here because
   // ArcSessionManager may or may not be alive at this point.
-  if (arc::ArcSessionManager* const session_manager =
-          arc::ArcSessionManager::Get()) {
-    session_manager->RemoveObserver(this);
+  if (arc::ArcSessionManager::Get()) {
+    arc_session_manager_observation_.Reset();
   }
 }
 
@@ -1733,7 +1731,7 @@
   // Registers a mount point for Android files only when the flag is enabled.
   RegisterAndroidFilesMountPoint();
   if (arc::ArcSessionManager::Get()) {
-    arc::ArcSessionManager::Get()->AddObserver(this);
+    arc_session_manager_observation_.Observe(arc::ArcSessionManager::Get());
   } else {
     // Can be NULL only in tests.
     CHECK_IS_TEST();
diff --git a/chrome/browser/ash/file_manager/volume_manager.h b/chrome/browser/ash/file_manager/volume_manager.h
index bd0a8c6..e0cb5c9 100644
--- a/chrome/browser/ash/file_manager/volume_manager.h
+++ b/chrome/browser/ash/file_manager/volume_manager.h
@@ -17,6 +17,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "chrome/browser/ash/arc/session/arc_session_manager.h"
 #include "chrome/browser/ash/arc/session/arc_session_manager_observer.h"
 #include "chrome/browser/ash/drive/drive_integration_service.h"
 #include "chrome/browser/ash/file_manager/documents_provider_root_manager.h"
@@ -405,6 +406,10 @@
   // Whether a read only version of local folders (My Files) is needed.
   bool read_only_local_folders_ = true;
 
+  base::ScopedObservation<arc::ArcSessionManager,
+                          arc::ArcSessionManagerObserver>
+      arc_session_manager_observation_{this};
+
   // Note: This should remain the last member so it'll be destroyed and
   // invalidate its weak pointers before any other members are destroyed.
   base::WeakPtrFactory<VolumeManager> weak_ptr_factory_{this};
diff --git a/chrome/browser/ash/login/app_mode/test/BUILD.gn b/chrome/browser/ash/login/app_mode/test/BUILD.gn
index c3f115b..42eea867 100644
--- a/chrome/browser/ash/login/app_mode/test/BUILD.gn
+++ b/chrome/browser/ash/login/app_mode/test/BUILD.gn
@@ -92,9 +92,7 @@
     "kiosk_sku_browsertest.cc",
     "kiosk_update_browsertest.cc",
     "web_kiosk_browsertest.cc",
-    "web_kiosk_controlled_frame_browsertest.cc",
     "web_kiosk_device_attributes_browsertest.cc",
-    "web_kiosk_direct_sockets_browsertest.cc",
     "web_kiosk_media_ui_browsertest.cc",
   ]
 
diff --git a/chrome/browser/ash/login/app_mode/test/web_kiosk_controlled_frame_browsertest.cc b/chrome/browser/ash/login/app_mode/test/web_kiosk_controlled_frame_browsertest.cc
deleted file mode 100644
index 686680f..0000000
--- a/chrome/browser/ash/login/app_mode/test/web_kiosk_controlled_frame_browsertest.cc
+++ /dev/null
@@ -1,237 +0,0 @@
-// 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 <memory>
-#include <string>
-#include <tuple>
-#include <vector>
-
-#include "base/functional/bind.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/version_info/channel.h"
-#include "chrome/browser/ash/login/app_mode/test/web_kiosk_base_test.h"
-#include "chrome/browser/ash/login/test/test_predicate_waiter.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/common/chrome_features.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/common/content_features.h"
-#include "content/public/test/browser_test.h"
-#include "content/public/test/browser_test_utils.h"
-#include "extensions/common/features/feature_channel.h"
-#include "net/http/http_status_code.h"
-#include "net/test/embedded_test_server/embedded_test_server.h"
-#include "net/test/embedded_test_server/http_response.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/features_generated.h"
-
-namespace {
-
-std::unique_ptr<net::test_server::HttpResponse> ServeSimpleHtmlPage(
-    const net::test_server::HttpRequest& request) {
-  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
-  http_response->set_code(net::HTTP_OK);
-  http_response->set_content_type("text/html");
-  http_response->set_content(
-      "<!DOCTYPE html>"
-      "<html lang=\"en\">"
-      "<head><title>Controlled Frame Test</title></head>"
-      "<body>A web page to test the Controlled Frame API availability.</body>"
-      "</html>");
-
-  return http_response;
-}
-
-void WaitForDocumentLoaded(content::WebContents* web_contents) {
-  ash::test::TestPredicateWaiter(
-      base::BindRepeating(
-          [](content::WebContents* web_contents) {
-            return content::EvalJs(web_contents,
-                                   "document.readyState === 'complete'")
-                .ExtractBool();
-          },
-          web_contents))
-      .Wait();
-}
-
-}  // namespace
-
-namespace ash {
-
-class WebKioskControlledFrameBaseTest : public WebKioskBaseTest {
- public:
-  WebKioskControlledFrameBaseTest() = delete;
-
-  void SetUpOnMainThread() override {
-    InitAppServer();
-    SetAppInstallUrl(web_app_server_.base_url());
-    WebKioskBaseTest::SetUpOnMainThread();
-  }
-
- protected:
-  WebKioskControlledFrameBaseTest(bool feature_enabled,
-                                  version_info::Channel channel,
-                                  bool https)
-      : feature_enabled_(feature_enabled),
-        channel_(channel),
-        https_(https),
-        web_app_server_(UseHttpsUrl()
-                            ? net::test_server::EmbeddedTestServer::TYPE_HTTPS
-                            : net::test_server::EmbeddedTestServer::TYPE_HTTP) {
-    std::vector<base::test::FeatureRef> enabled_features = {
-        features::kIsolatedWebApps, features::kWebKioskEnableIwaApis};
-    std::vector<base::test::FeatureRef> disabled_features;
-    if (feature_enabled_) {
-      enabled_features.push_back(blink::features::kControlledFrame);
-    } else {
-      disabled_features.push_back(blink::features::kControlledFrame);
-    }
-    feature_list_.InitWithFeatures(enabled_features, disabled_features);
-  }
-
-  bool UseHttpsUrl() { return https_; }
-
-  content::WebContents* TestSetup() {
-    InitializeRegularOnlineKiosk();
-    SelectFirstBrowser();
-
-    BrowserView* browser_view =
-        BrowserView::GetBrowserViewForBrowser(browser());
-    content::WebContents* web_contents =
-        browser_view ? browser_view->GetActiveWebContents() : nullptr;
-
-    WaitForDocumentLoaded(web_contents);
-    return web_contents;
-  }
-
-  const net::test_server::EmbeddedTestServer& web_app_server() {
-    return web_app_server_;
-  }
-
-  // Keep this in sync with controlled_frame_test_base.cc.
-  [[nodiscard]] bool CreateControlledFrame(content::RenderFrameHost* frame,
-                                           const GURL& src) {
-    static std::string kCreateControlledFrame = R"(
-    new Promise((resolve, reject) => {
-      const controlledframe = document.createElement('controlledframe');
-      if (!('src' in controlledframe)) {
-        // Tag is undefined or generates a malformed response.
-        reject('FAIL');
-        return;
-      }
-      controlledframe.setAttribute('src', $1);
-      controlledframe.addEventListener('loadstop', resolve);
-      controlledframe.addEventListener('loadabort', reject);
-      document.body.appendChild(controlledframe);
-    });
-)";
-    return ExecJs(frame, content::JsReplace(kCreateControlledFrame, src));
-  }
-
-  bool feature_enabled_{true};
-
- private:
-  void InitAppServer() {
-    web_app_server_.RegisterRequestHandler(
-        base::BindRepeating(&ServeSimpleHtmlPage));
-    ASSERT_TRUE(web_app_handle_ = web_app_server_.StartAndReturnHandle());
-  }
-
-  extensions::ScopedCurrentChannel channel_{version_info::Channel::CANARY};
-  bool https_{true};
-
-  net::test_server::EmbeddedTestServer web_app_server_;
-  net::test_server::EmbeddedTestServerHandle web_app_handle_;
-
-  base::test::ScopedFeatureList feature_list_;
-};
-
-class WebKioskControlledFrameHttpTest
-    : public WebKioskControlledFrameBaseTest,
-      public testing::WithParamInterface</*use_https=*/bool> {
- public:
-  WebKioskControlledFrameHttpTest()
-      : WebKioskControlledFrameBaseTest(
-            /*feature_enabled=*/true,
-            /*channel=*/version_info::Channel::CANARY,
-            /*https=*/GetParam()) {}
-};
-
-IN_PROC_BROWSER_TEST_P(WebKioskControlledFrameHttpTest,
-                       DISABLED_ApiAvailability) {
-  content::WebContents* web_contents = TestSetup();
-  ASSERT_NE(web_contents, nullptr);
-
-  // Controlled Frame API should be available for https urls, but not for http.
-  // Here we use the web app server's base URL. It'll be the same URL as the
-  // embedding app, but that doesn't matter. We only want to ensure that a
-  // generic HTTPS page can be loaded, and it's this test code that's just run
-  // the one time that adds the controlled frame tag to the embedding page.
-  bool is_api_available = CreateControlledFrame(
-      web_contents->GetPrimaryMainFrame(), web_app_server().base_url());
-  if (feature_enabled_ && UseHttpsUrl()) {
-    // TODO: crbug.com/355529251 - Fix expectation.
-    EXPECT_TRUE(is_api_available);
-  } else {
-    EXPECT_FALSE(is_api_available);
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(All, WebKioskControlledFrameHttpTest, testing::Bool());
-
-class WebKioskControlledFrameChannelTest
-    : public WebKioskControlledFrameBaseTest,
-      public testing::WithParamInterface<
-          std::tuple<bool, version_info::Channel>> {
- public:
-  WebKioskControlledFrameChannelTest()
-      : WebKioskControlledFrameBaseTest(
-            /*feature_enabled=*/std::get<0>(GetParam()),
-            /*channel=*/std::get<1>(GetParam()),
-            /*https=*/true) {}
-};
-
-// TODO(crbug.com/355290700): Re-enable this test
-IN_PROC_BROWSER_TEST_P(WebKioskControlledFrameChannelTest,
-                       DISABLED_ApiAvailability) {
-  content::WebContents* web_contents = TestSetup();
-  ASSERT_NE(web_contents, nullptr);
-
-  // Controlled Frame API should be available for non-stable / non-beta.
-  // This works because the mechanism for checking the channel runs using
-  // extensions-based code.
-  // Here we use the web app server's base URL. It'll be the same URL as the
-  // embedding app, but that doesn't matter. We only want to ensure that a
-  // generic HTTPS page can be loaded, and it's this test code that's just run
-  // the one time that adds the controlled frame tag to the embedding page.
-  bool is_api_available = CreateControlledFrame(
-      web_contents->GetPrimaryMainFrame(), web_app_server().base_url());
-  if (feature_enabled_ &&
-      extensions::GetCurrentChannel() != version_info::Channel::STABLE &&
-      extensions::GetCurrentChannel() != version_info::Channel::BETA) {
-    EXPECT_TRUE(is_api_available);
-  } else {
-    EXPECT_FALSE(is_api_available);
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    Enabled,
-    WebKioskControlledFrameChannelTest,
-    testing::Combine(
-        /*feature_enabled=*/testing::Values(true),
-        /*channel=*/testing::Values(version_info::Channel::STABLE,
-                                    version_info::Channel::BETA,
-                                    version_info::Channel::DEV,
-                                    version_info::Channel::CANARY,
-                                    version_info::Channel::DEFAULT)));
-
-INSTANTIATE_TEST_SUITE_P(
-    Disabled,
-    WebKioskControlledFrameChannelTest,
-    testing::Combine(
-        /*feature_enabled=*/testing::Values(false),
-        /*channel=*/testing::Values(version_info::Channel::STABLE,
-                                    version_info::Channel::CANARY)));
-
-}  // namespace ash
diff --git a/chrome/browser/ash/login/app_mode/test/web_kiosk_direct_sockets_browsertest.cc b/chrome/browser/ash/login/app_mode/test/web_kiosk_direct_sockets_browsertest.cc
deleted file mode 100644
index 1d789a50..0000000
--- a/chrome/browser/ash/login/app_mode/test/web_kiosk_direct_sockets_browsertest.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// 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 <memory>
-#include <string>
-
-#include "base/functional/bind.h"
-#include "base/test/scoped_feature_list.h"
-#include "chrome/browser/ash/login/app_mode/test/web_kiosk_base_test.h"
-#include "chrome/browser/ash/login/test/test_predicate_waiter.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_test.h"
-#include "content/public/test/browser_test_utils.h"
-#include "net/http/http_status_code.h"
-#include "net/test/embedded_test_server/embedded_test_server.h"
-#include "net/test/embedded_test_server/http_response.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/features_generated.h"
-#include "ui/base/metadata/base_type_conversion.h"
-
-namespace {
-
-std::unique_ptr<net::test_server::HttpResponse> ServeSimpleHtmlPage(
-    const net::test_server::HttpRequest& /*request*/) {
-  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
-  http_response->set_code(net::HTTP_OK);
-  http_response->set_content_type("text/html");
-  http_response->set_content(
-      "<!DOCTYPE html>"
-      "<html lang=\"en\">"
-      "<head><title>Direct Sockets Test</title></head>"
-      "<body>A web page to test the Direct Sockets API availability.</body>"
-      "</html>");
-
-  return http_response;
-}
-
-bool IsJsObjectDefined(content::WebContents* web_contents,
-                       const std::string& object_name) {
-  return content::EvalJs(web_contents, base::ReplaceStringPlaceholders(
-                                           "typeof $1 !== 'undefined'",
-                                           {object_name}, nullptr))
-      .ExtractBool();
-}
-
-void WaitForDocumentLoaded(content::WebContents* web_contents) {
-  ash::test::TestPredicateWaiter(
-      base::BindRepeating(
-          [](content::WebContents* web_contents) {
-            return content::EvalJs(web_contents,
-                                   "document.readyState === 'complete'")
-                .ExtractBool();
-          },
-          web_contents))
-      .Wait();
-}
-
-}  // namespace
-
-namespace ash {
-
-// TODO(b/326181857): Impl a common parent fixture for api availability tests.
-class WebKioskDirectSocketsTest : public WebKioskBaseTest {
- public:
-  void SetUpOnMainThread() override {
-    InitAppServer();
-    SetAppInstallUrl(web_app_server_.base_url());
-    WebKioskBaseTest::SetUpOnMainThread();
-  }
-
- protected:
-  content::WebContents* GetKioskAppWebContents() {
-    BrowserView* browser_view =
-        BrowserView::GetBrowserViewForBrowser(browser());
-    return browser_view ? browser_view->GetActiveWebContents() : nullptr;
-  }
-
- private:
-  void InitAppServer() {
-    web_app_server_.RegisterRequestHandler(
-        base::BindRepeating(&ServeSimpleHtmlPage));
-    ASSERT_TRUE(web_app_handle_ = web_app_server_.StartAndReturnHandle());
-  }
-
-  net::test_server::EmbeddedTestServer web_app_server_;
-  net::test_server::EmbeddedTestServerHandle web_app_handle_;
-
-  base::test::ScopedFeatureList feature_list_{blink::features::kDirectSockets};
-};
-
-// TODO(crbug.com/355290700): Re-enable this test
-IN_PROC_BROWSER_TEST_F(WebKioskDirectSocketsTest, DISABLED_ApiAvailability) {
-  InitializeRegularOnlineKiosk();
-  SelectFirstBrowser();
-
-  content::WebContents* web_contents = GetKioskAppWebContents();
-  ASSERT_NE(web_contents, nullptr);
-
-  WaitForDocumentLoaded(web_contents);
-
-  EXPECT_TRUE(IsJsObjectDefined(web_contents, "UDPSocket"));
-  EXPECT_TRUE(IsJsObjectDefined(web_contents, "TCPSocket"));
-  EXPECT_TRUE(IsJsObjectDefined(web_contents, "TCPServerSocket"));
-}
-
-}  // namespace ash
diff --git a/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.cc b/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.cc
index d7d1524..aa3238c 100644
--- a/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.cc
+++ b/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.cc
@@ -156,12 +156,16 @@
     // This error can only take place for windows builds which is not a part for
     // commercial CRD.
     case ErrorCode::ELEVATION_ERROR:
+    // This error is only reported on Mac.
+    case ErrorCode::LOGIN_SCREEN_NOT_SUPPORTED:
       return ExtendedStartCrdSessionResultCode::kFailureUnknownError;
     case ErrorCode::REAUTHZ_POLICY_CHECK_FAILED:
       return ExtendedStartCrdSessionResultCode::
           kFailureReauthzPolicyCheckFailed;
     case ErrorCode::NO_COMMON_AUTH_METHOD:
       return ExtendedStartCrdSessionResultCode::kFailureNoCommonAuthMethod;
+    case ErrorCode::SESSION_POLICIES_CHANGED:
+      return ExtendedStartCrdSessionResultCode::kFailureSessionPoliciesChanged;
   }
   NOTREACHED();
 }
@@ -209,6 +213,7 @@
     case ExtendedStartCrdSessionResultCode::kHostSessionDisconnected:
     case ExtendedStartCrdSessionResultCode::kFailureReauthzPolicyCheckFailed:
     case ExtendedStartCrdSessionResultCode::kFailureNoCommonAuthMethod:
+    case ExtendedStartCrdSessionResultCode::kFailureSessionPoliciesChanged:
       // The server side is not interested in a lot of the different CRD host
       // failures, which is why most of them are simply mapped to
       // 'FAILURE_CRD_HOST_ERROR`.
diff --git a/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.h b/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.h
index 1bd4ef0..86af03e 100644
--- a/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.h
+++ b/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.h
@@ -146,7 +146,10 @@
   // and the client.
   kFailureNoCommonAuthMethod = 31,
 
-  kMaxValue = kFailureNoCommonAuthMethod
+  // Failure because the session policies have changed.
+  kFailureSessionPoliciesChanged = 32,
+
+  kMaxValue = kFailureSessionPoliciesChanged
 };
 
 // Translates the error code.
diff --git a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java
index cbbe242..7e350db 100644
--- a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java
+++ b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java
@@ -86,7 +86,24 @@
                     if (mOnBackPressed != null) mOnBackPressed.run();
                     recordSystemBackCountIfBeforeFirstVisibleContent();
                     mLastCalledHandlerType = -1;
-                    BackPressManager.this.handleBackPress();
+                    if (ChromeFeatureList.sLockBackPressHandlerAtStart.isEnabled()
+                            && mActiveHandler != null) {
+                        Boolean enabled = mActiveHandler.getHandleBackPressChangedSupplier().get();
+                        if (enabled != null && enabled) {
+                            int result = mActiveHandler.handleBackPress();
+                            int index = BackPressManager.this.getIndex(mActiveHandler);
+                            mLastCalledHandlerType = index;
+                            if (result == BackPressResult.FAILURE) {
+                                BackPressManager.this.handleBackPress();
+                            } else {
+                                record(index);
+                            }
+                        } else {
+                            BackPressManager.this.handleBackPress();
+                        }
+                    } else {
+                        BackPressManager.this.handleBackPress();
+                    }
 
                     // This means this back is triggered by a gesture rather than the back button.
                     if (mLastBackEvent != null
@@ -348,6 +365,16 @@
         return null;
     }
 
+    private int getIndex(BackPressHandler handler) {
+        for (int i = 0; i < mHandlers.length; i++) {
+            if (mHandlers[i] == null) continue;
+            if (mHandlers[i] == handler) {
+                return i;
+            }
+        }
+        throw new AssertionError("Handler not found.");
+    }
+
     private void handleBackPress() {
         var failed = new ArrayList<String>();
         for (int i = 0; i < mHandlers.length; i++) {
diff --git a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerUnitTest.java b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerUnitTest.java
index a3280c0..f08afdb 100644
--- a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerUnitTest.java
+++ b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerUnitTest.java
@@ -109,6 +109,36 @@
     }
 
     @Test
+    public void testMaintainingHandler() {
+        BackPressManager manager = new BackPressManager();
+        manager.setIsGestureNavEnabledSupplier(() -> true);
+        EmptyBackPressHandler h1 = Mockito.spy(new EmptyBackPressHandler());
+        EmptyBackPressHandler h2 = Mockito.spy(new EmptyBackPressHandler());
+        manager.addHandler(h1, 0);
+        manager.addHandler(h2, 1);
+        h1.getHandleBackPressChangedSupplier().set(false);
+        h2.getHandleBackPressChangedSupplier().set(true);
+        Assert.assertEquals(
+                "Should return the active handler", h2, manager.getEnabledBackPressHandler());
+        var backEvent = new BackEventCompat(0, 0, 0, BackEventCompat.EDGE_LEFT);
+        manager.getCallback().handleOnBackStarted(backEvent);
+        Mockito.verify(h2).handleOnBackStarted(backEvent);
+
+        backEvent = new BackEventCompat(1, 0, .5f, BackEventCompat.EDGE_LEFT);
+        manager.getCallback().handleOnBackProgressed(backEvent);
+        Mockito.verify(h2).handleOnBackProgressed(backEvent);
+
+        backEvent = new BackEventCompat(2, 0, 1, BackEventCompat.EDGE_LEFT);
+        manager.getCallback().handleOnBackProgressed(backEvent);
+        Mockito.verify(h2).handleOnBackProgressed(backEvent);
+
+        h1.getHandleBackPressChangedSupplier().set(true);
+
+        manager.getCallback().handleOnBackPressed();
+        Mockito.verify(h2).handleBackPress();
+    }
+
+    @Test
     public void testMultipleHandlers() {
         BackPressManager manager = new BackPressManager();
         EmptyBackPressHandler h1 = new EmptyBackPressHandler();
diff --git a/chrome/browser/browser_features.h b/chrome/browser/browser_features.h
index 91081c22..6f61f7ad 100644
--- a/chrome/browser/browser_features.h
+++ b/chrome/browser/browser_features.h
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// WARNING: do not add new entries here. If a feature is only used in one
+// translation unit it should be inlined in that translation unit. If a feature
+// is referenced in multiple places, it should be scoped to that module, e.g.
+// //chrome/browser/<foo_module>/features.h
+
 // This file defines the browser-specific base::FeatureList features that are
 // not shared with other process types.
 
@@ -16,6 +21,10 @@
 
 namespace features {
 
+// WARNING: do not add new entries here. If a feature is only used in one
+// translation unit it should be inlined in that translation unit. If a feature
+// is referenced in multiple places, it should be scoped to that module, e.g.
+// //chrome/browser/<foo_module>/features.h
 // All features in alphabetical order. The features should be documented
 // alongside the definition of their values in the .cc file.
 
@@ -189,6 +198,11 @@
 
 BASE_DECLARE_FEATURE(kRemovalOfIWAsFromTabCapture);
 
+// WARNING: do not add new entries here. If a feature is only used in one
+// translation unit it should be inlined in that translation unit. If a feature
+// is referenced in multiple places, it should be scoped to that module, e.g.
+// //chrome/browser/<foo_module>/features.h
+//
 }  // namespace features
 
 #endif  // CHROME_BROWSER_BROWSER_FEATURES_H_
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index a351161..bba15cb 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -42,8 +42,7 @@
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/crash_upload_list/crash_upload_list.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
-#include "chrome/browser/dips/dips_service.h"
-#include "chrome/browser/dips/dips_service_factory.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "chrome/browser/domain_reliability/service_factory.h"
 #include "chrome/browser/downgrade/user_data_downgrade.h"
@@ -904,8 +903,7 @@
   }
 
   if (dips_mask != DIPSEventRemovalType::kNone) {
-    auto* dips_service = DIPSServiceFactory::GetForBrowserContext(profile_);
-    if (dips_service) {
+    if (DIPSServiceImpl* dips_service = DIPSServiceImpl::Get(profile_)) {
       dips_service->RemoveEvents(delete_begin_, delete_end_,
                                  filter_builder->BuildNetworkServiceFilter(),
                                  dips_mask);
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index 08c99b36a..7552a617 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -50,8 +50,8 @@
 #include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
-#include "chrome/browser/dips/dips_service.h"
-#include "chrome/browser/dips/dips_service_factory.h"
+#include "chrome/browser/dips/dips_service_impl.h"
+#include "chrome/browser/dips/dips_storage.h"
 #include "chrome/browser/domain_reliability/service_factory.h"
 #include "chrome/browser/download/chrome_download_manager_delegate.h"
 #include "chrome/browser/download/download_core_service_factory.h"
@@ -681,8 +681,7 @@
 class RemoveDIPSEventsTester {
  public:
   explicit RemoveDIPSEventsTester(Profile* profile) {
-    auto* dips_service = DIPSServiceFactory::GetForBrowserContext(profile);
-    storage_ = dips_service->storage();
+    storage_ = DIPSServiceImpl::Get(profile)->storage();
   }
 
   void WriteEventTimes(GURL url,
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 09d6cc5..fe6dae69 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -793,10 +793,6 @@
 #include "chrome/common/bound_session_request_throttled_handler.h"
 #endif  // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
 
-#if BUILDFLAG(IS_CHROMEOS)
-#include "chromeos/components/kiosk/kiosk_utils.h"
-#endif  // BUILDFLAG(IS_CHROMEOS)
-
 #if BUILDFLAG(ENTERPRISE_DATA_CONTROLS) && !BUILDFLAG(IS_ANDROID)
 #include "chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h"
 #include "chrome/browser/enterprise/data_protection/paste_allowed_request.h"
@@ -2608,13 +2604,6 @@
 bool ChromeContentBrowserClient::IsIsolatedContextAllowedForUrl(
     content::BrowserContext* browser_context,
     const GURL& lock_url) {
-#if BUILDFLAG(IS_CHROMEOS)
-  if (base::FeatureList::IsEnabled(features::kWebKioskEnableIwaApis) &&
-      chromeos::IsWebKioskSession()) {
-    return true;
-  }
-#endif
-
 #if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
   if (ChromeContentBrowserClientExtensionsPart::AreExtensionsDisabledForProfile(
           browser_context)) {
@@ -2949,47 +2938,49 @@
 
     // Please keep this in alphabetical order.
     static const char* const kSwitchNames[] = {
-      autofill::switches::kIgnoreAutocompleteOffForAutofill,
-      autofill::switches::kShowAutofillSignatures,
+        autofill::switches::kIgnoreAutocompleteOffForAutofill,
+        autofill::switches::kShowAutofillSignatures,
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-      switches::kShortMergeSessionTimeoutForTest,  // For tests only.
+        switches::kShortMergeSessionTimeoutForTest,  // For tests only.
 #endif
 #if BUILDFLAG(ENABLE_EXTENSIONS)
-      extensions::switches::kAllowHTTPBackgroundPage,
-      extensions::switches::kAllowLegacyExtensionManifests,
-      extensions::switches::kDisableExtensionsHttpThrottling,
-      extensions::switches::kEnableExperimentalExtensionApis,
-      extensions::switches::kExtensionsOnChromeURLs,
-      extensions::switches::kSetExtensionThrottleTestParams,  // For tests only.
-      extensions::switches::kAllowlistedExtensionID,
-      extensions::switches::kExtensionTestApiOnWebPages, // For tests only.
+        extensions::switches::kAllowHTTPBackgroundPage,
+        extensions::switches::kAllowLegacyExtensionManifests,
+        extensions::switches::kDisableExtensionsHttpThrottling,
+        extensions::switches::kEnableExperimentalExtensionApis,
+        extensions::switches::kExtensionsOnChromeURLs,
+        extensions::switches::kSetExtensionThrottleTestParams,  // For tests
+                                                                // only.
+        extensions::switches::kAllowlistedExtensionID,
+        extensions::switches::kExtensionTestApiOnWebPages,  // For tests only.
 #endif
-      switches::kAllowInsecureLocalhost,
-      switches::kAppsGalleryURL,
-      switches::kDisableJavaScriptHarmonyShipping,
-      variations::switches::kEnableBenchmarking,
-      switches::kEnableDistillabilityService,
-      switches::kEnableNaCl,
+        switches::kAllowInsecureLocalhost,
+        switches::kAppsGalleryURL,
+        switches::kDisableJavaScriptHarmonyShipping,
+        variations::switches::kEnableBenchmarking,
+        switches::kEnableDistillabilityService,
+        switches::kEnableNaCl,
 #if BUILDFLAG(ENABLE_NACL)
-      switches::kEnableNaClDebug,
+        switches::kEnableNaClDebug,
 #endif
-      switches::kEnableNetBenchmarking,
+        switches::kEnableNetBenchmarking,
+        switches::kExtensionAiDataCollection,
 #if BUILDFLAG(IS_CHROMEOS)
-      chromeos::switches::
-          kTelemetryExtensionPwaOriginOverrideForTesting,  // For tests only.
-      switches::kForceAppMode,
+        chromeos::switches::
+            kTelemetryExtensionPwaOriginOverrideForTesting,  // For tests only.
+        switches::kForceAppMode,
 #endif
 #if BUILDFLAG(ENABLE_NACL)
-      switches::kForcePNaClSubzero,
+        switches::kForcePNaClSubzero,
 #endif
-      switches::kForceUIDirection,
-      switches::kIgnoreGooglePortNumbers,
-      switches::kJavaScriptHarmony,
-      switches::kEnableExperimentalWebAssemblyFeatures,
-      embedder_support::kOriginTrialDisabledFeatures,
-      embedder_support::kOriginTrialPublicKey,
-      switches::kReaderModeHeuristics,
-      translate::switches::kTranslateSecurityOrigin,
+        switches::kForceUIDirection,
+        switches::kIgnoreGooglePortNumbers,
+        switches::kJavaScriptHarmony,
+        switches::kEnableExperimentalWebAssemblyFeatures,
+        embedder_support::kOriginTrialDisabledFeatures,
+        embedder_support::kOriginTrialPublicKey,
+        switches::kReaderModeHeuristics,
+        translate::switches::kTranslateSecurityOrigin,
     };
 
     command_line->CopySwitchesFrom(browser_command_line, kSwitchNames);
@@ -3000,6 +2991,7 @@
         extensions::switches::kEnableExperimentalExtensionApis,
         extensions::switches::kExtensionsOnChromeURLs,
         extensions::switches::kAllowlistedExtensionID,
+        switches::kExtensionAiDataCollection,
     };
 
     command_line->CopySwitchesFrom(browser_command_line, kSwitchNames);
diff --git a/chrome/browser/dips/chrome_dips_delegate.cc b/chrome/browser/dips/chrome_dips_delegate.cc
index 0ffa343..fffc2a6 100644
--- a/chrome/browser/dips/chrome_dips_delegate.cc
+++ b/chrome/browser/dips/chrome_dips_delegate.cc
@@ -9,6 +9,7 @@
 #include "base/check.h"
 #include "base/types/pass_key.h"
 #include "chrome/browser/dips/dips_browser_signin_detector.h"
+#include "chrome/browser/dips/stateful_bounce_counter.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_selections.h"
 #include "content/public/browser/dips_delegate.h"
@@ -48,4 +49,5 @@
     DIPSService* dips_service) {
   // Create DIPSBrowserSigninDetector.
   CHECK(DIPSBrowserSigninDetector::Get(browser_context));
+  dips::StatefulBounceCounter::CreateFor(dips_service);
 }
diff --git a/chrome/browser/dips/dips_bounce_detector.cc b/chrome/browser/dips/dips_bounce_detector.cc
index c45b02b5..b43acde 100644
--- a/chrome/browser/dips/dips_bounce_detector.cc
+++ b/chrome/browser/dips/dips_bounce_detector.cc
@@ -28,7 +28,7 @@
 #include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/dips/cookie_access_filter.h"
 #include "chrome/browser/dips/dips_redirect_info.h"
-#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/dips/dips_storage.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "content/public/browser/browser_context.h"
@@ -127,24 +127,7 @@
       &DIPSWebContentsObserver::EmitDIPSIssue, weak_factory_.GetWeakPtr());
 }
 
-DIPSWebContentsObserver::~DIPSWebContentsObserver() {
-  // Some UserData may interact with `this` during their destruction. Delete
-  // them now, before it's too late. If we don't delete them manually,
-  // ~SupportsUserData() will, but `this` will be invalid at that time.
-  ClearAllUserData();
-}
-
-DIPSWebContentsObserver::Observer::~Observer() {
-  CHECK(!IsInObserverList());
-}
-
-void DIPSWebContentsObserver::AddObserver(Observer* observer) {
-  observers_.AddObserver(observer);
-}
-
-void DIPSWebContentsObserver::RemoveObserver(const Observer* observer) {
-  observers_.RemoveObserver(observer);
-}
+DIPSWebContentsObserver::~DIPSWebContentsObserver() = default;
 
 RedirectChainDetector::RedirectChainDetector(content::WebContents* web_contents)
     : content::WebContentsObserver(web_contents),
@@ -618,9 +601,7 @@
     return;
   }
 
-  for (auto& observer : observers_) {
-    observer.OnStatefulBounce(web_contents());
-  }
+  dips_service_->NotifyStatefulBounce(web_contents());
 }
 
 // A thin wrapper around NavigationHandle to implement DIPSNavigationHandle.
diff --git a/chrome/browser/dips/dips_bounce_detector.h b/chrome/browser/dips/dips_bounce_detector.h
index 3d2d17b..919b51f7 100644
--- a/chrome/browser/dips/dips_bounce_detector.h
+++ b/chrome/browser/dips/dips_bounce_detector.h
@@ -14,13 +14,12 @@
 #include "base/memory/raw_ptr.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
-#include "base/supports_user_data.h"
 #include "base/time/default_clock.h"
 #include "base/timer/timer.h"
 #include "base/types/optional_ref.h"
 #include "chrome/browser/dips/cookie_access_filter.h"
 #include "chrome/browser/dips/dips_redirect_info.h"
-#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "content/public/browser/allow_service_worker_result.h"
 #include "content/public/browser/cookie_access_details.h"
@@ -445,22 +444,12 @@
       public content::WebContentsUserData<DIPSWebContentsObserver>,
       public content::SharedWorkerService::Observer,
       public content::DedicatedWorkerService::Observer,
-      public RedirectChainDetector::Observer,
-      public base::SupportsUserData {
+      public RedirectChainDetector::Observer {
  public:
   static void MaybeCreateForWebContents(content::WebContents* web_contents);
 
   ~DIPSWebContentsObserver() override;
 
-  class Observer : public base::CheckedObserver {
-   public:
-    ~Observer() override;
-    virtual void OnStatefulBounce(content::WebContents*) {}
-  };
-
-  void AddObserver(Observer* observer);
-  void RemoveObserver(const Observer* observer);
-
   // Use the passed handler instead of DIPSWebContentsObserver::EmitDIPSIssue().
   void SetIssueReportingCallbackForTesting(
       DIPSIssueReportingCallback callback) {
@@ -559,7 +548,6 @@
   std::optional<base::Time> last_storage_timestamp_;
   std::optional<base::Time> last_interaction_timestamp_;
 
-  base::ObserverList<Observer> observers_;
   base::WeakPtrFactory<DIPSWebContentsObserver> weak_factory_{this};
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
diff --git a/chrome/browser/dips/dips_bounce_detector_browsertest.cc b/chrome/browser/dips/dips_bounce_detector_browsertest.cc
index d3a5b630..342b21f2 100644
--- a/chrome/browser/dips/dips_bounce_detector_browsertest.cc
+++ b/chrome/browser/dips/dips_bounce_detector_browsertest.cc
@@ -38,6 +38,7 @@
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/dips/dips_redirect_info.h"
 #include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/dips/dips_storage.h"
 #include "chrome/browser/dips/dips_test_utils.h"
 #include "chrome/browser/dips/dips_utils.h"
diff --git a/chrome/browser/dips/dips_bounce_detector_unittest.cc b/chrome/browser/dips/dips_bounce_detector_unittest.cc
index 304458f..e6fd5b66 100644
--- a/chrome/browser/dips/dips_bounce_detector_unittest.cc
+++ b/chrome/browser/dips/dips_bounce_detector_unittest.cc
@@ -19,7 +19,7 @@
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "base/types/pass_key.h"
-#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/dips/dips_test_utils.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "components/content_settings/core/common/features.h"
diff --git a/chrome/browser/dips/dips_browser_signin_detector_factory.cc b/chrome/browser/dips/dips_browser_signin_detector_factory.cc
index 67b56b5..34f9ee2 100644
--- a/chrome/browser/dips/dips_browser_signin_detector_factory.cc
+++ b/chrome/browser/dips/dips_browser_signin_detector_factory.cc
@@ -5,12 +5,13 @@
 #include "chrome/browser/dips/dips_browser_signin_detector_factory.h"
 
 #include "chrome/browser/dips/chrome_dips_delegate.h"
+#include "chrome/browser/dips/dips_browser_signin_detector.h"
 #include "chrome/browser/dips/dips_service.h"
 #include "chrome/browser/dips/dips_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
-#include "dips_service_factory.h"
+#include "content/public/common/content_features.h"
 
 /*static*/
 DIPSBrowserSigninDetector* DIPSBrowserSigninDetector::Get(
@@ -73,7 +74,7 @@
 
   return std::make_unique<DIPSBrowserSigninDetector>(
       base::PassKey<DIPSBrowserSigninDetectorFactory>(),
-      DIPSServiceFactory::GetForBrowserContext(context),
+      DIPSService::Get(context),
       IdentityManagerFactory::GetForProfile(
           Profile::FromBrowserContext(context)));
 }
diff --git a/chrome/browser/dips/dips_cleanup_service_unittest.cc b/chrome/browser/dips/dips_cleanup_service_unittest.cc
index ecdb6ba..444faf76 100644
--- a/chrome/browser/dips/dips_cleanup_service_unittest.cc
+++ b/chrome/browser/dips/dips_cleanup_service_unittest.cc
@@ -7,7 +7,7 @@
 #include "base/files/file_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/test_file_util.h"
-#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/dips/dips_test_utils.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "chrome/test/base/testing_profile.h"
diff --git a/chrome/browser/dips/dips_helper_browsertest.cc b/chrome/browser/dips/dips_helper_browsertest.cc
index 8eee410..d04ad09 100644
--- a/chrome/browser/dips/dips_helper_browsertest.cc
+++ b/chrome/browser/dips/dips_helper_browsertest.cc
@@ -19,7 +19,7 @@
 #include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/dips/dips_bounce_detector.h"
-#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/dips/dips_storage.h"
 #include "chrome/browser/dips/dips_test_utils.h"
 #include "chrome/browser/dips/dips_utils.h"
diff --git a/chrome/browser/dips/dips_service.h b/chrome/browser/dips/dips_service.h
index 9866e6c1..e3655c1 100644
--- a/chrome/browser/dips/dips_service.h
+++ b/chrome/browser/dips/dips_service.h
@@ -1,40 +1,29 @@
-// Copyright 2022 The Chromium Authors
+// 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_DIPS_DIPS_SERVICE_H_
 #define CHROME_BROWSER_DIPS_DIPS_SERVICE_H_
 
-#include "base/functional/bind.h"
-#include "base/functional/callback_forward.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/observer_list_types.h"
-#include "base/run_loop.h"
-#include "base/task/sequenced_task_runner.h"
-#include "base/threading/sequence_bound.h"
-#include "base/types/pass_key.h"
-#include "chrome/browser/dips/dips_redirect_info.h"
-#include "chrome/browser/dips/dips_storage.h"
-#include "chrome/browser/dips/dips_utils.h"
-#include "components/keyed_service/core/keyed_service.h"
-#include "content/public/browser/browsing_data_filter_builder.h"
+#include <string>
+#include <vector>
 
-class DIPSServiceFactory;
+#include "base/functional/callback_forward.h"
+#include "base/observer_list_types.h"
+#include "base/supports_user_data.h"
+#include "base/time/time.h"
+#include "chrome/browser/dips/dips_redirect_info.h"
+
+class GURL;
 
 namespace content {
 class BrowserContext;
-class DipsDelegate;
+class WebContents;
 }  // namespace content
 
-namespace dips {
-class PersistentRepeatingTimer;
-}
-
 // When DIPS moves to //content, DIPSService will be exposed in the Content API,
 // available to embedders such as Chrome.
-class DIPSService {
+class DIPSService : public base::SupportsUserData {
  public:
   using DeletedSitesCallback =
       base::OnceCallback<void(const std::vector<std::string>& sites)>;
@@ -42,7 +31,8 @@
 
   class Observer : public base::CheckedObserver {
    public:
-    virtual void OnChainHandled(const DIPSRedirectChainInfoPtr& chain) = 0;
+    virtual void OnStatefulBounce(content::WebContents* web_contents) {}
+    virtual void OnChainHandled(const DIPSRedirectChainInfoPtr& chain) {}
   };
 
   static DIPSService* Get(content::BrowserContext* context);
@@ -63,152 +53,4 @@
   virtual void RemoveObserver(const Observer* observer) = 0;
 };
 
-// When DIPS moves to //content, DIPSServiceImpl will *not* be exposed in the
-// Content API. Only other code in //content (such as the DIPS implementation)
-// will be allowed to access it.
-class DIPSServiceImpl : public DIPSService, public KeyedService {
- public:
-  using RecordBounceCallback = base::RepeatingCallback<void(
-      const GURL& url,
-      bool has_3pc_exception,
-      const GURL& final_url,
-      base::Time time,
-      bool stateful,
-      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback)>;
-
-  DIPSServiceImpl(base::PassKey<DIPSServiceFactory>,
-                  content::BrowserContext* context);
-  ~DIPSServiceImpl() override;
-
-  static DIPSServiceImpl* Get(content::BrowserContext* context);
-
-  base::SequenceBound<DIPSStorage>* storage() { return &storage_; }
-  void RecordBounceForTesting(
-      const GURL& url,
-      bool has_3pc_exception,
-      const GURL& final_url,
-      base::Time time,
-      bool stateful,
-      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback) {
-    RecordBounce(url, has_3pc_exception, final_url, time, stateful,
-                 stateful_bounce_callback);
-  }
-
-  DIPSCookieMode GetCookieMode() const;
-
-  void RemoveEvents(const base::Time& delete_begin,
-                    const base::Time& delete_end,
-                    network::mojom::ClearDataFilterPtr filter,
-                    const DIPSEventRemovalType type);
-
-  // This allows for deletion of state for sites deemed eligible when evaluated
-  // with no grace period.
-  void DeleteEligibleSitesImmediately(DeletedSitesCallback callback) override;
-
-  void HandleRedirectChain(
-      std::vector<DIPSRedirectInfoPtr> redirects,
-      DIPSRedirectChainInfoPtr chain,
-      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback);
-
-  void RecordInteractionForTesting(const GURL& url) override;
-
-  void DidSiteHaveInteractionSince(
-      const GURL& url,
-      base::Time bound,
-      CheckInteractionCallback callback) const override;
-
-  // This allows unit-testing the metrics emitted by HandleRedirect() without
-  // instantiating DIPSService.
-  static void HandleRedirectForTesting(const DIPSRedirectInfo& redirect,
-                                       const DIPSRedirectChainInfo& chain,
-                                       RecordBounceCallback callback) {
-    HandleRedirect(redirect, chain, callback,
-                   base::BindRepeating([](const GURL& final_url) {}));
-  }
-
-  void SetStorageClockForTesting(base::Clock* clock) {
-    DCHECK(storage_);
-    storage_.AsyncCall(&DIPSStorage::SetClockForTesting).WithArgs(clock);
-  }
-
-  void OnTimerFiredForTesting() { OnTimerFired(); }
-  void WaitForFileDeletionCompleteForTesting() {
-    wait_for_file_deletion_.Run();
-  }
-
-  void AddObserver(Observer* observer) override;
-  void RemoveObserver(const Observer* observer) override;
-
-  void AddOpenSite(const std::string& site) {
-    if (open_sites_.contains(site)) {
-      open_sites_.at(site)++;
-    } else {
-      open_sites_.insert({site, 1});
-    }
-  }
-
-  void RemoveOpenSite(const std::string& site) {
-    CHECK(open_sites_.contains(site));
-    if (open_sites_.contains(site)) {
-      open_sites_.at(site)--;
-      if (open_sites_.at(site) == 0) {
-        open_sites_.erase(site);
-      }
-    }
-  }
-
-  // The first time this method is called, it calls
-  // DipsDelegate::OnDipsServiceCreated(). On subsequent calls, it does nothing.
-  void MaybeNotifyCreated(base::PassKey<DIPSServiceFactory>);
-
- private:
-  std::unique_ptr<dips::PersistentRepeatingTimer> CreateTimer();
-
-  void GotState(
-      std::vector<DIPSRedirectInfoPtr> redirects,
-      DIPSRedirectChainInfoPtr chain,
-      size_t index,
-      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback,
-      const DIPSState url_state);
-  void RecordBounce(
-      const GURL& url,
-      bool has_3pc_exception,
-      const GURL& final_url,
-      base::Time time,
-      bool stateful,
-      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback);
-  static void HandleRedirect(
-      const DIPSRedirectInfo& redirect,
-      const DIPSRedirectChainInfo& chain,
-      RecordBounceCallback callback,
-      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback);
-
-  scoped_refptr<base::SequencedTaskRunner> CreateTaskRunner();
-
-  void OnTimerFired();
-  void DeleteDIPSEligibleState(DeletedSitesCallback callback,
-                               std::vector<std::string> sites_to_clear);
-  void RunDeletionTaskOnUIThread(std::vector<std::string> sites_to_clear,
-                                 base::OnceClosure callback);
-
-  // DIPSService overrides:
-  void RecordBrowserSignIn(std::string_view domain) override;
-
-  base::RunLoop wait_for_file_deletion_;
-  raw_ptr<content::BrowserContext> browser_context_;
-  // The persisted timer controlling how often incidental state is cleared.
-  // This timer is null if the DIPS feature isn't enabled with a valid TimeDelta
-  // given for its `timer_delay` parameter.
-  // See base/time/time_delta_from_string.h for how that param should be given.
-  std::unique_ptr<dips::PersistentRepeatingTimer> repeating_timer_;
-  base::SequenceBound<DIPSStorage> storage_;
-  base::ObserverList<Observer> observers_;
-  std::unique_ptr<content::DipsDelegate> dips_delegate_;
-  bool delegate_notified_ = false;
-
-  std::map<std::string, int> open_sites_;
-
-  base::WeakPtrFactory<DIPSServiceImpl> weak_factory_{this};
-};
-
 #endif  // CHROME_BROWSER_DIPS_DIPS_SERVICE_H_
diff --git a/chrome/browser/dips/dips_service_factory.cc b/chrome/browser/dips/dips_service_factory.cc
index 5846b1f1a..213fc9f 100644
--- a/chrome/browser/dips/dips_service_factory.cc
+++ b/chrome/browser/dips/dips_service_factory.cc
@@ -6,7 +6,7 @@
 
 #include "base/no_destructor.h"
 #include "chrome/browser/dips/chrome_dips_delegate.h"
-#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 
 using PassKey = base::PassKey<DIPSServiceFactory>;
diff --git a/chrome/browser/dips/dips_service.cc b/chrome/browser/dips/dips_service_impl.cc
similarity index 96%
rename from chrome/browser/dips/dips_service.cc
rename to chrome/browser/dips/dips_service_impl.cc
index 21f77a77..0316b20 100644
--- a/chrome/browser/dips/dips_service.cc
+++ b/chrome/browser/dips/dips_service_impl.cc
@@ -1,8 +1,8 @@
-// Copyright 2022 The Chromium Authors
+// 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/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 
 #include <memory>
 #include <optional>
@@ -306,7 +306,17 @@
                           base::Unretained(this)));
 }
 
-DIPSServiceImpl::~DIPSServiceImpl() = default;
+DIPSServiceImpl::~DIPSServiceImpl() {
+  // Some UserData may interact with `this` during their destruction. Delete
+  // them now, before it's too late. If we don't delete them manually,
+  // ~SupportsUserData() will, but `this` will be invalid at that time.
+  //
+  // Note that we can't put this call in ~DIPSService() either, even though
+  // DIPSService is the class that directly inherits from SupportsUserData.
+  // Because when ~DIPSService() is called, it's undefined behavior to call
+  // pure virtual functions like DIPSService::RemoveObserver().
+  ClearAllUserData();
+}
 
 /* static */
 DIPSServiceImpl* DIPSServiceImpl::Get(content::BrowserContext* context) {
@@ -681,3 +691,9 @@
   delegate_notified_ = true;  // Set this first to prevent infinite recursion.
   ChromeDipsDelegate::Create()->OnDipsServiceCreated(browser_context_, this);
 }
+
+void DIPSServiceImpl::NotifyStatefulBounce(content::WebContents* web_contents) {
+  for (auto& observer : observers_) {
+    observer.OnStatefulBounce(web_contents);
+  }
+}
diff --git a/chrome/browser/dips/dips_service_impl.h b/chrome/browser/dips/dips_service_impl.h
new file mode 100644
index 0000000..9336f49
--- /dev/null
+++ b/chrome/browser/dips/dips_service_impl.h
@@ -0,0 +1,187 @@
+// 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_DIPS_DIPS_SERVICE_IMPL_H_
+#define CHROME_BROWSER_DIPS_DIPS_SERVICE_IMPL_H_
+
+#include "base/functional/bind.h"
+#include "base/functional/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list_types.h"
+#include "base/run_loop.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/threading/sequence_bound.h"
+#include "base/types/pass_key.h"
+#include "chrome/browser/dips/dips_redirect_info.h"
+#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_storage.h"
+#include "chrome/browser/dips/dips_utils.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "content/public/browser/browsing_data_filter_builder.h"
+
+class DIPSServiceFactory;
+
+namespace content {
+class BrowserContext;
+class DipsDelegate;
+}  // namespace content
+
+namespace dips {
+class PersistentRepeatingTimer;
+}
+
+// When DIPS moves to //content, DIPSServiceImpl will *not* be exposed in the
+// Content API. Only other code in //content (such as the DIPS implementation)
+// will be allowed to access it.
+class DIPSServiceImpl : public DIPSService, public KeyedService {
+ public:
+  using RecordBounceCallback = base::RepeatingCallback<void(
+      const GURL& url,
+      bool has_3pc_exception,
+      const GURL& final_url,
+      base::Time time,
+      bool stateful,
+      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback)>;
+
+  DIPSServiceImpl(base::PassKey<DIPSServiceFactory>,
+                  content::BrowserContext* context);
+  ~DIPSServiceImpl() override;
+
+  static DIPSServiceImpl* Get(content::BrowserContext* context);
+
+  base::SequenceBound<DIPSStorage>* storage() { return &storage_; }
+  void RecordBounceForTesting(
+      const GURL& url,
+      bool has_3pc_exception,
+      const GURL& final_url,
+      base::Time time,
+      bool stateful,
+      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback) {
+    RecordBounce(url, has_3pc_exception, final_url, time, stateful,
+                 stateful_bounce_callback);
+  }
+
+  DIPSCookieMode GetCookieMode() const;
+
+  void RemoveEvents(const base::Time& delete_begin,
+                    const base::Time& delete_end,
+                    network::mojom::ClearDataFilterPtr filter,
+                    const DIPSEventRemovalType type);
+
+  // This allows for deletion of state for sites deemed eligible when evaluated
+  // with no grace period.
+  void DeleteEligibleSitesImmediately(DeletedSitesCallback callback) override;
+
+  void HandleRedirectChain(
+      std::vector<DIPSRedirectInfoPtr> redirects,
+      DIPSRedirectChainInfoPtr chain,
+      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback);
+
+  void RecordInteractionForTesting(const GURL& url) override;
+
+  void DidSiteHaveInteractionSince(
+      const GURL& url,
+      base::Time bound,
+      CheckInteractionCallback callback) const override;
+
+  // This allows unit-testing the metrics emitted by HandleRedirect() without
+  // instantiating DIPSService.
+  static void HandleRedirectForTesting(const DIPSRedirectInfo& redirect,
+                                       const DIPSRedirectChainInfo& chain,
+                                       RecordBounceCallback callback) {
+    HandleRedirect(redirect, chain, callback,
+                   base::BindRepeating([](const GURL& final_url) {}));
+  }
+
+  void SetStorageClockForTesting(base::Clock* clock) {
+    DCHECK(storage_);
+    storage_.AsyncCall(&DIPSStorage::SetClockForTesting).WithArgs(clock);
+  }
+
+  void OnTimerFiredForTesting() { OnTimerFired(); }
+  void WaitForFileDeletionCompleteForTesting() {
+    wait_for_file_deletion_.Run();
+  }
+
+  void AddObserver(Observer* observer) override;
+  void RemoveObserver(const Observer* observer) override;
+
+  void AddOpenSite(const std::string& site) {
+    if (open_sites_.contains(site)) {
+      open_sites_.at(site)++;
+    } else {
+      open_sites_.insert({site, 1});
+    }
+  }
+
+  void RemoveOpenSite(const std::string& site) {
+    CHECK(open_sites_.contains(site));
+    if (open_sites_.contains(site)) {
+      open_sites_.at(site)--;
+      if (open_sites_.at(site) == 0) {
+        open_sites_.erase(site);
+      }
+    }
+  }
+
+  // The first time this method is called, it calls
+  // DipsDelegate::OnDipsServiceCreated(). On subsequent calls, it does nothing.
+  void MaybeNotifyCreated(base::PassKey<DIPSServiceFactory>);
+
+  // Notify Observers that a stateful bounce took place in `web_contents`.
+  void NotifyStatefulBounce(content::WebContents* web_contents);
+
+ private:
+  std::unique_ptr<dips::PersistentRepeatingTimer> CreateTimer();
+
+  void GotState(
+      std::vector<DIPSRedirectInfoPtr> redirects,
+      DIPSRedirectChainInfoPtr chain,
+      size_t index,
+      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback,
+      const DIPSState url_state);
+  void RecordBounce(
+      const GURL& url,
+      bool has_3pc_exception,
+      const GURL& final_url,
+      base::Time time,
+      bool stateful,
+      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback);
+  static void HandleRedirect(
+      const DIPSRedirectInfo& redirect,
+      const DIPSRedirectChainInfo& chain,
+      RecordBounceCallback callback,
+      base::RepeatingCallback<void(const GURL&)> stateful_bounce_callback);
+
+  scoped_refptr<base::SequencedTaskRunner> CreateTaskRunner();
+
+  void OnTimerFired();
+  void DeleteDIPSEligibleState(DeletedSitesCallback callback,
+                               std::vector<std::string> sites_to_clear);
+  void RunDeletionTaskOnUIThread(std::vector<std::string> sites_to_clear,
+                                 base::OnceClosure callback);
+
+  // DIPSService overrides:
+  void RecordBrowserSignIn(std::string_view domain) override;
+
+  base::RunLoop wait_for_file_deletion_;
+  raw_ptr<content::BrowserContext> browser_context_;
+  // The persisted timer controlling how often incidental state is cleared.
+  // This timer is null if the DIPS feature isn't enabled with a valid TimeDelta
+  // given for its `timer_delay` parameter.
+  // See base/time/time_delta_from_string.h for how that param should be given.
+  std::unique_ptr<dips::PersistentRepeatingTimer> repeating_timer_;
+  base::SequenceBound<DIPSStorage> storage_;
+  base::ObserverList<Observer> observers_;
+  std::unique_ptr<content::DipsDelegate> dips_delegate_;
+  bool delegate_notified_ = false;
+
+  std::map<std::string, int> open_sites_;
+
+  base::WeakPtrFactory<DIPSServiceImpl> weak_factory_{this};
+};
+
+#endif  // CHROME_BROWSER_DIPS_DIPS_SERVICE_IMPL_H_
diff --git a/chrome/browser/dips/dips_service_unittest.cc b/chrome/browser/dips/dips_service_unittest.cc
index ff381af5..0f40072 100644
--- a/chrome/browser/dips/dips_service_unittest.cc
+++ b/chrome/browser/dips/dips_service_unittest.cc
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/dips/dips_service.h"
-
 #include <optional>
 
 #include "base/files/file_util.h"
@@ -23,6 +21,7 @@
 #include "chrome/browser/dips/dips_bounce_detector.h"
 #include "chrome/browser/dips/dips_redirect_info.h"
 #include "chrome/browser/dips/dips_service_factory.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/dips/dips_state.h"
 #include "chrome/browser/dips/dips_test_utils.h"
 #include "chrome/browser/dips/dips_utils.h"
diff --git a/chrome/browser/dips/dips_test_utils.cc b/chrome/browser/dips/dips_test_utils.cc
index ced51da..0f2e01d 100644
--- a/chrome/browser/dips/dips_test_utils.cc
+++ b/chrome/browser/dips/dips_test_utils.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/dips/dips_cleanup_service_factory.h"
 #include "chrome/browser/dips/dips_service.h"
 #include "chrome/browser/dips/dips_service_factory.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
 #include "content/public/test/browser_test_utils.h"
diff --git a/chrome/browser/dips/dips_test_utils.h b/chrome/browser/dips/dips_test_utils.h
index 5d4297a..c3895eb 100644
--- a/chrome/browser/dips/dips_test_utils.h
+++ b/chrome/browser/dips/dips_test_utils.h
@@ -16,6 +16,7 @@
 #include "base/types/expected.h"
 #include "chrome/browser/dips/dips_redirect_info.h"
 #include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "chrome/browser/profiles/profile_test_util.h"
 #include "components/ukm/test_ukm_recorder.h"
diff --git a/chrome/browser/dips/stateful_bounce_counter.cc b/chrome/browser/dips/stateful_bounce_counter.cc
index e60751c..2c6b64f 100644
--- a/chrome/browser/dips/stateful_bounce_counter.cc
+++ b/chrome/browser/dips/stateful_bounce_counter.cc
@@ -12,26 +12,21 @@
 
 namespace dips {
 
-StatefulBounceCounter::StatefulBounceCounter(PassKey,
-                                             DIPSWebContentsObserver* dips_wco)
-    : dips_wco_(dips_wco) {
-  dips_wco_->AddObserver(this);
+StatefulBounceCounter::StatefulBounceCounter(PassKey, DIPSService* dips_service)
+    : dips_service_(dips_service) {
+  dips_service_->AddObserver(this);
 }
 
 StatefulBounceCounter::~StatefulBounceCounter() {
-  dips_wco_->RemoveObserver(this);
+  dips_service_->RemoveObserver(this);
 }
 
 /*static*/
-StatefulBounceCounter* StatefulBounceCounter::Get(
-    DIPSWebContentsObserver* dips_wco) {
-  if (void* data = dips_wco->GetUserData(&kUserDataKey)) {
-    return static_cast<StatefulBounceCounter*>(data);
-  }
-  auto counter = std::make_unique<StatefulBounceCounter>(PassKey(), dips_wco);
-  StatefulBounceCounter* p = counter.get();  // grab a pointer before moving it.
-  dips_wco->SetUserData(&kUserDataKey, std::move(counter));
-  return p;
+void StatefulBounceCounter::CreateFor(DIPSService* dips_service) {
+  CHECK(!dips_service->GetUserData(&kUserDataKey));
+  dips_service->SetUserData(
+      &kUserDataKey,
+      std::make_unique<StatefulBounceCounter>(PassKey(), dips_service));
 }
 
 void StatefulBounceCounter::OnStatefulBounce(
diff --git a/chrome/browser/dips/stateful_bounce_counter.h b/chrome/browser/dips/stateful_bounce_counter.h
index fda96bb..f03e6de 100644
--- a/chrome/browser/dips/stateful_bounce_counter.h
+++ b/chrome/browser/dips/stateful_bounce_counter.h
@@ -8,30 +8,31 @@
 #include "base/memory/raw_ptr.h"
 #include "base/supports_user_data.h"
 #include "base/types/pass_key.h"
-#include "chrome/browser/dips/dips_bounce_detector.h"
 #include "chrome/browser/dips/dips_service.h"
-#include "content/public/browser/web_contents_user_data.h"
 
 namespace dips {
 
 // This class exists just to call
 // PageSpecificContentSettings::IncrementStatefulBounceCount() whenever the user
 // is statefully bounced.
-class StatefulBounceCounter : public DIPSWebContentsObserver::Observer,
+class StatefulBounceCounter : public DIPSService::Observer,
                               public base::SupportsUserData::Data {
  public:
   using PassKey = base::PassKey<StatefulBounceCounter>;
-  // The constructor takes a PassKey so only Get() can call std::make_unique().
-  StatefulBounceCounter(PassKey, DIPSWebContentsObserver*);
+
+  // The constructor takes a PassKey so only CreateFor() can call
+  // std::make_unique<StatefulBounceCounter>().
+  StatefulBounceCounter(PassKey, DIPSService*);
   ~StatefulBounceCounter() override;
 
-  // Get the instance for `dips_wco`, creating it if it doesn't exist yet.
-  static StatefulBounceCounter* Get(DIPSWebContentsObserver* dips_wco);
+  // Create a StatefulBounceCounter that observes `dips_service` and will be
+  // destroyed automatically.
+  static void CreateFor(DIPSService* dips_service);
 
   void OnStatefulBounce(content::WebContents*) override;
 
  private:
-  raw_ptr<DIPSWebContentsObserver> dips_wco_;
+  raw_ptr<DIPSService> dips_service_;
 
   // For SupportsUserData:
   static const int kUserDataKey = 0;
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
index 0297c8a..2b837ce 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
@@ -17,6 +17,7 @@
 #include <vector>
 
 #include "base/apple/bridging.h"
+#include "base/apple/foundation_util.h"
 #include "base/apple/scoped_cftyperef.h"
 #include "base/containers/span.h"
 #include "base/numerics/safe_conversions.h"
@@ -98,9 +99,7 @@
     return false;
   }
 
-  auto data =
-      base::make_span(CFDataGetBytePtr(data_ref),
-                      base::checked_cast<size_t>(CFDataGetLength(data_ref)));
+  auto data = base::apple::CFDataToSpan(data_ref);
   bssl::UniquePtr<EC_GROUP> p256(
       EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
   bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get()));
@@ -271,9 +270,8 @@
     return false;
   }
 
-  output.assign(
-      CFDataGetBytePtr(signature.get()),
-      CFDataGetBytePtr(signature.get()) + CFDataGetLength(signature.get()));
+  auto signature_span = base::apple::CFDataToSpan(signature.get());
+  output.assign(signature_span.begin(), signature_span.end());
   return true;
 }
 
diff --git a/chrome/browser/extensions/api/experimental_ai_data/experimental_ai_data_api.cc b/chrome/browser/extensions/api/experimental_ai_data/experimental_ai_data_api.cc
index 76e4c5a..b6dfea6d 100644
--- a/chrome/browser/extensions/api/experimental_ai_data/experimental_ai_data_api.cc
+++ b/chrome/browser/extensions/api/experimental_ai_data/experimental_ai_data_api.cc
@@ -10,11 +10,13 @@
 
 #include "base/metrics/field_trial_params.h"
 #include "base/strings/string_split.h"
+#include "base/version_info/channel.h"
 #include "chrome/browser/ai/ai_data_keyed_service.h"
 #include "chrome/browser/ai/ai_data_keyed_service_factory.h"
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/common/channel_info.h"
 #include "chrome/common/extensions/api/experimental_ai_data.h"
 #include "components/optimization_guide/proto/features/model_prototyping.pb.h"
 #include "content/public/browser/web_contents.h"
@@ -52,6 +54,14 @@
     return RespondNow(Error("API access restricted for this extension."));
   }
 
+  // In addition to the extension framework channel restriction, we make sure
+  // the API is not available on Stable. In particular,
+  // extension::switches::kEnableExperimentalExtensionApis allows ignoring those
+  // channel restrictions.
+  if (chrome::GetChannel() == version_info::Channel::STABLE) {
+    return RespondNow(Error("API access restricted to non-Stable channels."));
+  }
+
   auto params = api::experimental_ai_data::GetAiData::Params::Create(args());
 
   content::WebContents* web_contents = nullptr;
diff --git a/chrome/browser/extensions/api/permissions/permissions_api.cc b/chrome/browser/extensions/api/permissions/permissions_api.cc
index 18bc247..1fb047f 100644
--- a/chrome/browser/extensions/api/permissions/permissions_api.cc
+++ b/chrome/browser/extensions/api/permissions/permissions_api.cc
@@ -64,8 +64,10 @@
     "any of its host permissions.";
 constexpr char kExtensionRequestCannotBeRemovedError[] =
     "Extension cannot remove a site access request that doesn't exist.";
-constexpr char kInvalidPatternError[] =
+constexpr char kAddRequestInvalidPatternError[] =
     "Extension cannot add a request with an invalid value for 'pattern'.";
+constexpr char kRemoveRequestInvalidPatternError[] =
+    "Extension cannot remove a request with an invalid value for 'pattern'.";
 
 PermissionsRequestFunction::DialogAction g_dialog_action =
     PermissionsRequestFunction::DialogAction::kDefault;
@@ -132,6 +134,12 @@
   return true;
 }
 
+// Returns whether `pattern` was successfully parsed into `parsed_pattern`.
+bool ParsePattern(const std::string& pattern, URLPattern& parsed_pattern) {
+  parsed_pattern.SetValidSchemes(Extension::kValidHostPermissionSchemes);
+  return parsed_pattern.Parse(pattern) == URLPattern::ParseResult::kSuccess;
+}
+
 }  // namespace
 
 ExtensionFunction::ResponseAction PermissionsContainsFunction::Run() {
@@ -575,10 +583,9 @@
   std::optional<std::string> pattern_param = params->request.pattern;
   std::optional<URLPattern> pattern;
   if (pattern_param) {
-    URLPattern parsed_pattern(Extension::kValidHostPermissionSchemes);
-    if (parsed_pattern.Parse(*pattern_param) !=
-        URLPattern::ParseResult::kSuccess) {
-      return RespondNow(Error(kInvalidPatternError));
+    URLPattern parsed_pattern;
+    if (!ParsePattern(*pattern_param, parsed_pattern)) {
+      return RespondNow(Error(kAddRequestInvalidPatternError));
     }
     pattern = parsed_pattern;
   }
@@ -637,17 +644,17 @@
   const std::optional<std::string>& document_id_param =
       params->request.document_id;
   std::optional<int> tab_id_param = params->request.tab_id;
-  // TODO(crbug.com/330588494): Add `pattern` parameter.
 
+  // Removal is invalid if it has both document and tab id.
   if ((!document_id_param && !tab_id_param) ||
       (document_id_param && tab_id_param)) {
     return RespondNow(Error(kMustSpecifyDocumentIdOrTabIdError));
   }
 
-  // Values to be computed for either tab id or document id.
   content::WebContents* web_contents = nullptr;
   int tab_id = -1;
 
+  // Removal is invalid if document or tab id are not valid.
   bool is_valid = false;
   std::string error;
   if (tab_id_param) {
@@ -668,12 +675,24 @@
     return RespondNow(Error(error));
   }
 
+  // Removal is invalid if pattern provided cannot be parsed.
+  std::optional<std::string> pattern_param = params->request.pattern;
+  std::optional<URLPattern> pattern;
+  if (pattern_param) {
+    URLPattern parsed_pattern;
+    if (!ParsePattern(*pattern_param, parsed_pattern)) {
+      return RespondNow(Error(kRemoveRequestInvalidPatternError));
+    }
+    pattern = parsed_pattern;
+  }
+
   // Verify we properly retrieved the necessary information.
   DCHECK(web_contents);
   DCHECK_NE(tab_id, -1);
 
-  bool is_removed = PermissionsManager::Get(browser_context())
-                        ->RemoveSiteAccessRequest(tab_id, extension()->id());
+  bool is_removed =
+      PermissionsManager::Get(browser_context())
+          ->RemoveSiteAccessRequest(tab_id, extension()->id(), pattern);
   if (!is_removed) {
     return RespondNow(Error(kExtensionRequestCannotBeRemovedError));
   }
diff --git a/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc b/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc
index e1608a9..0388e57 100644
--- a/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc
+++ b/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc
@@ -1299,10 +1299,9 @@
   }
 }
 
-// Tests extension can remove a site access request for a tab, if request is
-// existent.
+// Tests extension cannot remove a site access request that doesn't exist.
 TEST_F(PermissionsAPISiteAccessRequestsUnitTest,
-       RemoveSiteAccessRequest_TabId) {
+       RemoveSiteAccessRequest_TabId_Invalid) {
   scoped_refptr<const Extension> extension =
       ExtensionBuilder("Extension")
           .SetManifestKey("host_permissions",
@@ -1317,14 +1316,13 @@
 
   auto* permissions_manager = PermissionsManager::Get(profile());
 
-  // Remove site access request for tab, when it has no active requests.
+  // Extension cannot remove a request when there is no current request.
   {
-    auto function =
+    auto remove_function =
         base::MakeRefCounted<PermissionsRemoveSiteAccessRequestFunction>();
-    function->set_extension(extension.get());
-
+    remove_function->set_extension(extension.get());
     std::string error = api_test_utils::RunFunctionAndReturnError(
-        function.get(), GetFunctionParams(tab_id), profile(),
+        remove_function.get(), GetFunctionParams(tab_id), profile(),
         api_test_utils::FunctionMode::kNone);
     EXPECT_EQ(
         "Extension cannot remove a site access request that doesn't exist.",
@@ -1335,28 +1333,176 @@
         tab_id, extension->id()));
   }
 
-  // Add site access request for tab.
+  // Extension cannot remove a site access request with a pattern when it
+  // doesn't match the active request (that matches all patterns).
   {
-    auto function =
+    // Add a site access request without a pattern. Not specifying a pattern
+    // means request will be shown for all patterns.
+    auto add_function =
         base::MakeRefCounted<PermissionsAddSiteAccessRequestFunction>();
-    function->set_extension(extension.get());
+    add_function->set_extension(extension.get());
     EXPECT_TRUE(api_test_utils::RunFunction(
-        function.get(), GetFunctionParams(tab_id), profile(),
+        add_function.get(), GetFunctionParams(tab_id), profile(),
         api_test_utils::FunctionMode::kNone));
 
     // Verify site access request was added.
     EXPECT_TRUE(permissions_manager->HasActiveSiteAccessRequest(
         tab_id, extension->id()));
+
+    // Remove a site access request with 'requested.com' pattern. Even though
+    // existent request matches all patterns, the removal must exactly match
+    // request. We do this because we don't support "all urls but <x>".
+    auto remove_function =
+        base::MakeRefCounted<PermissionsRemoveSiteAccessRequestFunction>();
+    remove_function->set_extension(extension.get());
+    std::string error = api_test_utils::RunFunctionAndReturnError(
+        remove_function.get(),
+        GetFunctionParams(tab_id, /*pattern=*/"*://*.requested.com/*"),
+        profile(), api_test_utils::FunctionMode::kNone);
+    EXPECT_EQ(
+        "Extension cannot remove a site access request that doesn't exist.",
+        error);
+
+    // Verify request wasn't removed.
+    EXPECT_TRUE(permissions_manager->HasActiveSiteAccessRequest(
+        tab_id, extension->id()));
   }
 
-  // Remove site access request for tab, when it has an active requests.
+  // Extension cannot remove a site access request with a pattern when it
+  // doesn't match the active request (with a different pattern specified).
   {
-    auto function =
-        base::MakeRefCounted<PermissionsRemoveSiteAccessRequestFunction>();
-    function->set_extension(extension.get());
-
+    // Add a site access request with 'requested.com' pattern. Adding a new
+    // request overrides existent request.
+    auto add_function =
+        base::MakeRefCounted<PermissionsAddSiteAccessRequestFunction>();
+    add_function->set_extension(extension.get());
     EXPECT_TRUE(api_test_utils::RunFunction(
-        function.get(), GetFunctionParams(tab_id), profile(),
+        add_function.get(), GetFunctionParams(tab_id, "*://*.requested.com/*"),
+        profile(), api_test_utils::FunctionMode::kNone));
+
+    // Verify site access request was added.
+    EXPECT_TRUE(permissions_manager->HasActiveSiteAccessRequest(
+        tab_id, extension->id()));
+
+    // Remove a site access request with a 'other.com' pattern. Function is
+    // invalid because 'other.com' doesn't match with the current request for
+    // 'requested.com'.
+    auto remove_function =
+        base::MakeRefCounted<PermissionsRemoveSiteAccessRequestFunction>();
+    remove_function->set_extension(extension.get());
+    std::string error = api_test_utils::RunFunctionAndReturnError(
+        remove_function.get(), GetFunctionParams(tab_id, "*://*.other.com/*"),
+        profile(), api_test_utils::FunctionMode::kNone);
+    EXPECT_EQ(
+        "Extension cannot remove a site access request that doesn't exist.",
+        error);
+
+    // Verify request wasn't removed.
+    EXPECT_TRUE(permissions_manager->HasActiveSiteAccessRequest(
+        tab_id, extension->id()));
+  }
+}
+
+// Tests extension can remove a site access request that matches an existent
+// request.
+TEST_F(PermissionsAPISiteAccessRequestsUnitTest,
+       RemoveSiteAccessRequest_TabId_Valid) {
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("Extension")
+          .SetManifestKey("host_permissions",
+                          base::Value::List().Append("*://*.requested.com/*"))
+          .Build();
+  AddExtensionAndWithheldPermissions(*extension);
+
+  // Open tab on a url requested by the extension.
+  NavigateTo("http://www.requested.com");
+  int tab_id = ExtensionTabUtil::GetTabId(
+      browser()->tab_strip_model()->GetActiveWebContents());
+
+  auto* permissions_manager = PermissionsManager::Get(profile());
+
+  // Extension can remove a site access request that matches all patterns (by
+  // not specifying one) when it matches the active request (that matches all
+  // patterns).
+  {
+    // Add a site access request without a pattern.
+    auto add_function =
+        base::MakeRefCounted<PermissionsAddSiteAccessRequestFunction>();
+    add_function->set_extension(extension.get());
+    EXPECT_TRUE(api_test_utils::RunFunction(
+        add_function.get(), GetFunctionParams(tab_id), profile(),
+        api_test_utils::FunctionMode::kNone));
+
+    // Verify site access request was added.
+    EXPECT_TRUE(permissions_manager->HasActiveSiteAccessRequest(
+        tab_id, extension->id()));
+
+    // Remove a site access request without a pattern.
+    auto remove_function =
+        base::MakeRefCounted<PermissionsRemoveSiteAccessRequestFunction>();
+    remove_function->set_extension(extension.get());
+    EXPECT_TRUE(api_test_utils::RunFunction(
+        remove_function.get(), GetFunctionParams(tab_id), profile(),
+        api_test_utils::FunctionMode::kNone));
+
+    // Verify request was removed.
+    EXPECT_FALSE(permissions_manager->HasActiveSiteAccessRequest(
+        tab_id, extension->id()));
+  }
+
+  // Extension can remove a site access request with a pattern when it matches
+  // the current request (with the same pattern).
+  {
+    // Add a site access request with 'requested.com' pattern.
+    auto add_function =
+        base::MakeRefCounted<PermissionsAddSiteAccessRequestFunction>();
+    add_function->set_extension(extension.get());
+    EXPECT_TRUE(api_test_utils::RunFunction(
+        add_function.get(),
+        GetFunctionParams(tab_id, /*pattern=*/"*://*.requested.com/*"),
+        profile(), api_test_utils::FunctionMode::kNone));
+
+    // Verify site access request was added.
+    EXPECT_TRUE(permissions_manager->HasActiveSiteAccessRequest(
+        tab_id, extension->id()));
+
+    // Remove a site access request with 'requested.com' pattern.
+    auto remove_function =
+        base::MakeRefCounted<PermissionsRemoveSiteAccessRequestFunction>();
+    remove_function->set_extension(extension.get());
+    EXPECT_TRUE(api_test_utils::RunFunction(
+        remove_function.get(), GetFunctionParams(tab_id), profile(),
+        api_test_utils::FunctionMode::kNone));
+
+    // Verify request was removed.
+    EXPECT_FALSE(permissions_manager->HasActiveSiteAccessRequest(
+        tab_id, extension->id()));
+  }
+
+  // Extension can remove a site access request without a pattern when it
+  // matches the active request (with a pattern).
+  {
+    // Add a site access request with 'requested.com' pattern.
+    auto add_function =
+        base::MakeRefCounted<PermissionsAddSiteAccessRequestFunction>();
+    add_function->set_extension(extension.get());
+    EXPECT_TRUE(api_test_utils::RunFunction(
+        add_function.get(),
+        GetFunctionParams(tab_id, /*pattern=*/"*://*.requested.com/*"),
+        profile(), api_test_utils::FunctionMode::kNone));
+
+    // Verify site access request was added.
+    EXPECT_TRUE(permissions_manager->HasActiveSiteAccessRequest(
+        tab_id, extension->id()));
+
+    // Remove a site access request without specifying pattern (which matches to
+    // all patterns). Function is valid because it matches the current request
+    // ('all patterns' which matches current request on 'requested.com').
+    auto remove_function =
+        base::MakeRefCounted<PermissionsRemoveSiteAccessRequestFunction>();
+    remove_function->set_extension(extension.get());
+    EXPECT_TRUE(api_test_utils::RunFunction(
+        remove_function.get(), GetFunctionParams(tab_id), profile(),
         api_test_utils::FunctionMode::kNone));
 
     // Verify request was removed.
@@ -1367,6 +1513,8 @@
 
 // Tests extension can remove a site access request for a document, if request
 // is existent.
+// Note: Document id is converted to tab id. Thus, here we only need to test the
+// base cases since we have extensive testing for removing requests with tab id.
 TEST_F(PermissionsAPISiteAccessRequestsUnitTest,
        RemoveSiteAccessRequest_DocumentId) {
   scoped_refptr<const Extension> extension =
diff --git a/chrome/browser/extensions/extension_sync_service.cc b/chrome/browser/extensions/extension_sync_service.cc
index ba4ee41..ec387bcf 100644
--- a/chrome/browser/extensions/extension_sync_service.cc
+++ b/chrome/browser/extensions/extension_sync_service.cc
@@ -46,10 +46,6 @@
 using extensions::SyncBundle;
 
 namespace {
-BASE_FEATURE(kBookmarkAppDeletion,
-             "BookmarkAppDeletion",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Returns true if the sync type of |extension| matches |type|.
 bool IsCorrectSyncType(const Extension& extension, syncer::DataType type) {
   return (type == syncer::EXTENSIONS && extension.is_extension()) ||
@@ -308,8 +304,7 @@
 
   // Remove all deprecated bookmark apps immediately, as they aren't loaded into
   // the extensions system at all (and thus cannot be looked up).
-  if (base::FeatureList::IsEnabled(kBookmarkAppDeletion) &&
-      extension_sync_data.is_deprecated_bookmark_app()) {
+  if (extension_sync_data.is_deprecated_bookmark_app()) {
     GetSyncBundle(syncer::APPS)->ApplySyncData(extension_sync_data);
     GetSyncBundle(syncer::APPS)
         ->PushSyncDeletion(id, extension_sync_data.GetSyncData());
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 73847f4..e04589a 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -150,6 +150,11 @@
     "expiry_milestone": 150
   },
   {
+    "name": "android-app-integration-with-favicon",
+    "owners": ["hanxi@chromium.org", "gangwu@chromium.org", "clank-home@google.com" ],
+    "expiry_milestone": 150
+  },
+  {
     "name": "android-bottom-toolbar",
     "owners": [ "pnoland@chromium.org","gangwu@chromium.org","lazzzis@google.com" ],
     "expiry_milestone": 150
@@ -162,7 +167,7 @@
   {
     "name": "android-elegant-text-height",
     "owners": [ "twellington@chromium.org", "clank-app-team@google.com" ],
-    "expiry_milestone": 130
+    "expiry_milestone": 134
   },
   {
     "name": "android-extended-keyboard-shortcuts",
@@ -853,11 +858,6 @@
     "expiry_milestone": 130
   },
   {
-    "name": "autofill-enable-virtual-cards",
-    "owners": [ "siyua@chromium.org", "chbannon@google.com", "slobodan@chromium.org" ],
-    "expiry_milestone": 128
-  },
-  {
     "name": "autofill-for-unclassified-fields-available",
     "owners": [ "tchudakov@chromium.org", "vykochko@chromium.org", "brunobraga@chromium.org" ],
     "expiry_milestone": 140
@@ -3177,6 +3177,11 @@
     "expiry_milestone": 130
   },
   {
+    "name": "enable-extension-ai-data-collection",
+    "owners": [ "ryansturm@chromium.org" ],
+    "expiry_milestone": 150
+  },
+  {
     "name": "enable-external-display-hdr10",
     "owners": [ "sashamcintosh@chromium.org", "chromeos-gfx@google.com" ],
     "expiry_milestone": 140
@@ -4507,10 +4512,6 @@
     "owners": [ "pmolinalopez@chromium.org", "andrescj@chromium.org" ],
     "expiry_milestone": 128
   },
-  {"name": "extension-ai-data-collection",
-  "owners": [ "ryansturm@chromium.org" ],
-  "expiry_milestone": 150
-},
   {
     "name": "extension-manifest-v2-deprecation-disabled",
     "owners": [ "emiliapaz@chromium.org" ],
@@ -6495,11 +6496,6 @@
     "expiry_milestone": 132
   },
   {
-    "name": "omnibox-drop-unrecognized-templateurl-parameters",
-    "owners": [ "ender@google.com", "jdonnelly@chromium.org" ],
-    "expiry_milestone": 140
-  },
-  {
     "name": "omnibox-dynamic-max-autocomplete",
     "owners": [ "stkhapugin@chromium.org", "manukh@chromium.org", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 120
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 51c9b2de..c8351ac2 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -58,6 +58,12 @@
 const char kAndroidAppIntegrationDescription[] =
     "If enabled, allows Chrome to integrate with the Android App Search.";
 
+const char kAndroidAppIntegrationWithFaviconName[] =
+    "Integrate with Android App Search with favicons";
+const char kAndroidAppIntegrationWithFaviconDescription[] =
+    "If enabled, allows Chrome to integrate with the Android App Search with "
+    "favicons.";
+
 const char kAndroidBottomToolbarName[] = "Bottom Toolbar";
 const char kAndroidBottomToolbarDescription[] =
     "If enabled, displays the toolbar at the bottom.";
@@ -332,12 +338,6 @@
     "Enable UI improvements for downloads, download scanning, and download "
     "warnings. The enabled features are subject to change at any time.";
 
-extern const char kDropUnrecognizedTemplateUrlParametersName[] =
-    "Drop unrecognized TemplateURL parameters";
-extern const char kDropUnrecognizedTemplateUrlParametersDescription[] =
-    "When enabled, any unrecognized TemplateURL parameters will be removed "
-    "when performing replacement.";
-
 const char kEnableBenchmarkingName[] = "Enable benchmarking";
 const char kEnableBenchmarkingDescription[] =
     "Sets all features to a fixed state; that is, disables randomization for "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index ad5ea17..4c16db4 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -63,6 +63,9 @@
 extern const char kAndroidAppIntegrationName[];
 extern const char kAndroidAppIntegrationDescription[];
 
+extern const char kAndroidAppIntegrationWithFaviconName[];
+extern const char kAndroidAppIntegrationWithFaviconDescription[];
+
 extern const char kAndroidBottomToolbarName[];
 extern const char kAndroidBottomToolbarDescription[];
 
@@ -213,9 +216,6 @@
 extern const char kDownloadWarningImprovementsName[];
 extern const char kDownloadWarningImprovementsDescription[];
 
-extern const char kDropUnrecognizedTemplateUrlParametersName[];
-extern const char kDropUnrecognizedTemplateUrlParametersDescription[];
-
 extern const char kEnableBenchmarkingName[];
 extern const char kEnableBenchmarkingDescription[];
 extern const char kEnableBenchmarkingChoiceDisabled[];
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index d8b8783..ffbdb20c 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -173,6 +173,7 @@
     &kRedirectExplicitCTAIntentsToExistingActivity,
     &kAllowNewIncognitoTabIntents,
     &kAndroidAppIntegration,
+    &kAndroidAppIntegrationWithFavicon,
     &kAndroidBottomToolbar,
     &kAndroidElegantTextHeight,
     &kAndroidGoogleSansText,
@@ -253,6 +254,7 @@
     &kFullscreenInsetsApiMigration,
     &kFullscreenInsetsApiMigrationOnAutomotive,
     &kGtsCloseTabAnimationKillSwitch,
+    &kLockBackPressHandlerAtStart,
     &kIncognitoReauthenticationForAndroid,
     &kIncognitoScreenshot,
     &kLensOnQuickActionSearchWidget,
@@ -439,6 +441,10 @@
              "AndroidAppIntegration",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kAndroidAppIntegrationWithFavicon,
+             "AndroidAppIntegrationWithFavicon",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kAndroidBottomToolbar,
              "AndroidBottomToolbar",
              base::FEATURE_DISABLED_BY_DEFAULT);
@@ -733,6 +739,10 @@
              "GtsCloseTabAnimationKillSwitch",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kLockBackPressHandlerAtStart,
+             "LockBackPressHandlerAtStart",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 BASE_FEATURE(kIncognitoReauthenticationForAndroid,
              "IncognitoReauthenticationForAndroid",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 52bb306..08b6bbd 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -18,6 +18,7 @@
 BASE_DECLARE_FEATURE(kAdaptiveButtonInTopToolbarPageSummary);
 BASE_DECLARE_FEATURE(kAllowNewIncognitoTabIntents);
 BASE_DECLARE_FEATURE(kAndroidAppIntegration);
+BASE_DECLARE_FEATURE(kAndroidAppIntegrationWithFavicon);
 BASE_DECLARE_FEATURE(kAndroidBottomToolbar);
 BASE_DECLARE_FEATURE(kAndroidElegantTextHeight);
 BASE_DECLARE_FEATURE(kAndroidGoogleSansText);
@@ -104,6 +105,7 @@
 BASE_DECLARE_FEATURE(kFullscreenInsetsApiMigration);
 BASE_DECLARE_FEATURE(kFullscreenInsetsApiMigrationOnAutomotive);
 BASE_DECLARE_FEATURE(kGtsCloseTabAnimationKillSwitch);
+BASE_DECLARE_FEATURE(kLockBackPressHandlerAtStart);
 BASE_DECLARE_FEATURE(kIncognitoReauthenticationForAndroid);
 BASE_DECLARE_FEATURE(kIncognitoScreenshot);
 BASE_DECLARE_FEATURE(kImprovedA2HS);
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 d803bc2..7438407 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
@@ -161,6 +161,8 @@
             "AdaptiveButtonInTopToolbarCustomizationV2";
     public static final String ALLOW_NEW_INCOGNITO_TAB_INTENTS = "AllowNewIncognitoTabIntents";
     public static final String ANDROID_APP_INTEGRATION = "AndroidAppIntegration";
+    public static final String ANDROID_APP_INTEGRATION_WITH_FAVICON =
+            "AndroidAppIntegrationWithFavicon";
     public static final String ANDROID_BOTTOM_TOOLBAR = "AndroidBottomToolbar";
     public static final String ANDROID_ELEGANT_TEXT_HEIGHT = "AndroidElegantTextHeight";
     public static final String ANDROID_GOOGLE_SANS_TEXT = "AndroidGoogleSansText";
@@ -344,6 +346,7 @@
             "FullscreenInsetsApiMigrationOnAutomotive";
     public static final String GTS_CLOSE_TAB_ANIMATION_KILL_SWITCH =
             "GtsCloseTabAnimationKillSwitch";
+    public static final String LOCK_BACK_PRESS_HANDLER_AT_START = "LockBackPressHandlerAtStart";
     public static final String HASH_PREFIX_REAL_TIME_LOOKUPS =
             "SafeBrowsingHashPrefixRealTimeLookups";
     public static final String HISTORY_JOURNEYS = "Journeys";
@@ -559,6 +562,8 @@
             newCachedFlag(ACCOUNT_REAUTHENTICATION_RECENT_TIME_WINDOW, true);
     public static final CachedFlag sAndroidAppIntegration =
             newCachedFlag(ANDROID_APP_INTEGRATION, false);
+    public static final CachedFlag sAndroidAppIntegrationWithFavicon =
+            newCachedFlag(ANDROID_APP_INTEGRATION_WITH_FAVICON, false);
     public static final CachedFlag sAndroidBottomToolbar =
             newCachedFlag(ANDROID_BOTTOM_TOOLBAR, false);
     public static final CachedFlag sAndroidElegantTextHeight =
@@ -653,6 +658,8 @@
             newCachedFlag(FULLSCREEN_INSETS_API_MIGRATION, false);
     public static final CachedFlag sFullscreenInsetsApiMigrationOnAutomotive =
             newCachedFlag(FULLSCREEN_INSETS_API_MIGRATION_ON_AUTOMOTIVE, true);
+    public static final CachedFlag sLockBackPressHandlerAtStart =
+            newCachedFlag(LOCK_BACK_PRESS_HANDLER_AT_START, true);
     public static final CachedFlag sIncognitoReauthenticationForAndroid =
             newCachedFlag(INCOGNITO_REAUTHENTICATION_FOR_ANDROID, true);
     public static final CachedFlag sLogoPolish = newCachedFlag(LOGO_POLISH, true);
@@ -742,6 +749,7 @@
             List.of(
                     sAccountReauthenticationRecentTimeWindow,
                     sAndroidAppIntegration,
+                    sAndroidAppIntegrationWithFavicon,
                     sAndroidBottomToolbar,
                     sAndroidElegantTextHeight,
                     sAndroidGoogleSansText,
@@ -791,6 +799,7 @@
                     sFullscreenInsetsApiMigration,
                     sFullscreenInsetsApiMigrationOnAutomotive,
                     sIncognitoReauthenticationForAndroid,
+                    sLockBackPressHandlerAtStart,
                     sLogoPolish,
                     sLogoPolishAnimationKillSwitch,
                     sMagicStackAndroid,
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 5d5ef20..883d46d 100644
--- a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
+++ b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
@@ -127,6 +127,7 @@
 #endif
 
 #if BUILDFLAG(IS_MAC)
+#include "base/mac/process_requirement.h"
 #include "chrome/common/chrome_version.h"
 #endif  // BUILDFLAG(IS_MAC)
 
@@ -879,6 +880,10 @@
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   RecordChromeOSChannel();
 #endif
+
+#if BUILDFLAG(IS_MAC)
+  base::mac::ProcessRequirement::MaybeGatherMetrics();
+#endif
 }
 
 }  // namespace
diff --git a/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc b/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc
index 155850b..f4d6cc7 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc
@@ -185,8 +185,7 @@
 
  private:
   void OnPredictionUpdated(
-      const std::optional<NavigationPredictorKeyedService::Prediction>
-          prediction) override {
+      const NavigationPredictorKeyedService::Prediction& prediction) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
     ++count_predictions_;
     last_prediction_ = prediction;
diff --git a/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.cc b/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.cc
index 374b500..99f531f 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.cc
@@ -149,8 +149,9 @@
 
   last_prediction_ = Prediction(web_contents, document_url, prediction_source,
                                 sorted_predicted_urls);
+
   for (auto& observer : observer_list_) {
-    observer.OnPredictionUpdated(last_prediction_);
+    observer.OnPredictionUpdated(last_prediction_.value());
   }
 
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
@@ -163,7 +164,7 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   observer_list_.AddObserver(observer);
   if (last_prediction_.has_value()) {
-    observer->OnPredictionUpdated(last_prediction_);
+    observer->OnPredictionUpdated(last_prediction_.value());
   }
 }
 
diff --git a/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h b/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h
index 5edc843e..4957c2a 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h
+++ b/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h
@@ -86,8 +86,7 @@
   // notifications.
   class Observer {
    public:
-    virtual void OnPredictionUpdated(
-        const std::optional<Prediction> prediction) = 0;
+    virtual void OnPredictionUpdated(const Prediction& prediction) = 0;
 
    protected:
     Observer() {}
diff --git a/chrome/browser/obsolete_system/OWNERS b/chrome/browser/obsolete_system/OWNERS
index 6f66759..190e0daf 100644
--- a/chrome/browser/obsolete_system/OWNERS
+++ b/chrome/browser/obsolete_system/OWNERS
@@ -5,7 +5,7 @@
 avi@chromium.org
 
 # Windows
-brucedawson@chromium.org
+jessemckenna@google.com
 
 # Linux
 thestig@chromium.org
diff --git a/chrome/browser/optimization_guide/chrome_hints_manager.cc b/chrome/browser/optimization_guide/chrome_hints_manager.cc
index 10c8c5a7..b264eb5 100644
--- a/chrome/browser/optimization_guide/chrome_hints_manager.cc
+++ b/chrome/browser/optimization_guide/chrome_hints_manager.cc
@@ -24,18 +24,14 @@
 
 // Returns true if we can make a request for hints for |prediction|.
 bool IsAllowedToFetchForNavigationPrediction(
-    const std::optional<NavigationPredictorKeyedService::Prediction>
-        prediction) {
-  DCHECK(prediction);
-
-  if (prediction->prediction_source() !=
+    const NavigationPredictorKeyedService::Prediction& prediction) {
+  if (prediction.prediction_source() !=
       NavigationPredictorKeyedService::PredictionSource::
           kAnchorElementsParsedFromWebPage) {
     // We only support predictions from page anchors.
     return false;
   }
-  const std::optional<GURL> source_document_url =
-      prediction->source_document_url();
+  const auto& source_document_url = prediction.source_document_url();
   if (!source_document_url || source_document_url->is_empty())
     return false;
 
@@ -93,17 +89,16 @@
 }
 
 void ChromeHintsManager::OnPredictionUpdated(
-    const std::optional<NavigationPredictorKeyedService::Prediction>
-        prediction) {
+    const NavigationPredictorKeyedService::Prediction& prediction) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(prediction);
 
-  if (!IsAllowedToFetchForNavigationPrediction(prediction))
+  if (!IsAllowedToFetchForNavigationPrediction(prediction)) {
     return;
+  }
 
   // Per comments in NavigationPredictorKeyedService::Prediction, this pointer
   // should be valid while OnPredictionUpdated is on the call stack.
-  content::WebContents* web_contents = prediction->web_contents();
+  content::WebContents* web_contents = prediction.web_contents();
   CHECK(web_contents);
   auto* observer =
       OptimizationGuideWebContentsObserver::FromWebContents(web_contents);
@@ -112,7 +107,7 @@
   }
 
   std::vector<GURL> urls_to_fetch;
-  for (const auto& url : prediction->sorted_predicted_urls()) {
+  for (const auto& url : prediction.sorted_predicted_urls()) {
     if (!IsAllowedToFetchNavigationHints(url))
       continue;
     // Don't prefetch hints for SRP links that point back to Google.
diff --git a/chrome/browser/optimization_guide/chrome_hints_manager.h b/chrome/browser/optimization_guide/chrome_hints_manager.h
index 22c7943..39786ed5 100644
--- a/chrome/browser/optimization_guide/chrome_hints_manager.h
+++ b/chrome/browser/optimization_guide/chrome_hints_manager.h
@@ -40,8 +40,7 @@
 
   // NavigationPredictorKeyedService::Observer:
   void OnPredictionUpdated(
-      const std::optional<NavigationPredictorKeyedService::Prediction>
-          prediction) override;
+      const NavigationPredictorKeyedService::Prediction& prediction) override;
 
  private:
   // A reference to the profile. Not owned.
diff --git a/chrome/browser/page_load_metrics/observers/non_tab_webui_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/non_tab_webui_page_load_metrics_observer.cc
index 23a56830..aabc004 100644
--- a/chrome/browser/page_load_metrics/observers/non_tab_webui_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/non_tab_webui_page_load_metrics_observer.cc
@@ -4,21 +4,30 @@
 
 #include "chrome/browser/page_load_metrics/observers/non_tab_webui_page_load_metrics_observer.h"
 
+#include <optional>
+
 #include "base/strings/strcat.h"
+#include "base/time/time.h"
 #include "base/trace_event/named_trigger.h"
 #include "chrome/browser/ui/webui/top_chrome/webui_contents_preload_manager.h"
+#include "components/page_load_metrics/browser/page_load_metrics_observer_delegate.h"
 #include "components/page_load_metrics/browser/page_load_metrics_util.h"
 #include "content/public/common/url_constants.h"
 
-const char kNonTabWebUINavigationToLCPHistogramName[] =
+constexpr char kNonTabWebUINavigationToLCPHistogramName[] =
     "PageLoad.PaintTiming.NavigationToLargestContentfulPaint2.NonTabWebUI";
 
-const char kNonTabWebUINavigationToFCPHistogramName[] =
+constexpr char kNonTabWebUINavigationToFCPHistogramName[] =
     "PageLoad.PaintTiming.NavigationToFirstContentfulPaint.NonTabWebUI";
 
-const char kNonTabWebUIRequestToFCPHistogramName[] =
+constexpr char kNonTabWebUIRequestToLCPHistogramName[] =
+    "WebUI.TopChrome.RequestToLCP";
+
+constexpr char kNonTabWebUIRequestToFCPHistogramName[] =
     "WebUI.TopChrome.RequestToFCP";
 
+namespace {
+
 std::string GetSuffixedLCPHistogram(std::string_view webui_name) {
   return base::StrCat(
       {kNonTabWebUINavigationToLCPHistogramName, ".", webui_name});
@@ -29,10 +38,41 @@
       {kNonTabWebUINavigationToFCPHistogramName, ".", webui_name});
 }
 
+std::string GetSuffixedRequestToLCPHistogram(std::string_view webui_name) {
+  return base::StrCat({kNonTabWebUIRequestToLCPHistogramName, ".", webui_name});
+}
+
 std::string GetSuffixedRequestToFCPHistogram(std::string_view webui_name) {
   return base::StrCat({kNonTabWebUIRequestToFCPHistogramName, ".", webui_name});
 }
 
+// We define "background time" as the amount of time between navigation and
+// when user opens the WebUI. When a WebUI is preloaded, navigation can happen
+// well before the user opens the WebUI.
+base::TimeDelta GetBackgroundTime(
+    const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
+  const std::optional<base::TimeTicks> request_time =
+      WebUIContentsPreloadManager::GetInstance()->GetRequestTime(
+          delegate.GetWebContents());
+  if (!request_time.has_value()) {
+    // The WebUIContentsPreloadManager may not have a record of when the user
+    // opened the WebUI. This may happen in unit tests, or if a non-tab WebUI
+    // is opened in a tab for debugging purposes. In these cases, we define the
+    // "background time" to be zero.
+    return base::TimeDelta();
+  }
+
+  const base::TimeTicks last_navigation_time = delegate.GetNavigationStart();
+  // The request time is earlier than the last navigation time if the WebUI
+  // refreshes or redirects. In this case the WebUI is never in the background
+  // since last navigation.
+  const base::TimeDelta background_time =
+      std::max(*request_time - last_navigation_time, base::TimeDelta());
+  return background_time;
+}
+
+}  // namespace
+
 NonTabPageLoadMetricsObserver::NonTabPageLoadMetricsObserver(
     const std::string& webui_name)
     : page_load_metrics::PageLoadMetricsObserver(), webui_name_(webui_name) {
@@ -53,22 +93,9 @@
   PAGE_LOAD_HISTOGRAM(GetSuffixedFCPHistogram(webui_name_),
                       first_contentful_paint);
 
-  // Time from request to LCP and FCP. These metrics exclude the time when the
-  // preloaded WebUI is in the background.
-  const std::optional<base::TimeTicks> request_time =
-      WebUIContentsPreloadManager::GetInstance()->GetRequestTime(
-          GetDelegate().GetWebContents());
-  if (!request_time.has_value()) {
-    return;
-  }
-
-  const base::TimeTicks last_navigation_time =
-      GetDelegate().GetNavigationStart();
-  // The request time is earlier than the last navigation time if the page
-  // refreshes or redirects. In this case the page is never in the background
-  // since last navigation.
-  const base::TimeDelta background_time =
-      std::max(*request_time - last_navigation_time, base::TimeDelta());
+  // Time from request to FCP. This metric disregards time spent in the
+  // background, which is non-zero when the WebUI is preloaded.
+  base::TimeDelta background_time = GetBackgroundTime(GetDelegate());
   PAGE_LOAD_SHORT_HISTOGRAM(kNonTabWebUIRequestToFCPHistogramName,
                             first_contentful_paint - background_time);
   PAGE_LOAD_SHORT_HISTOGRAM(GetSuffixedRequestToFCPHistogram(webui_name_),
@@ -83,12 +110,23 @@
               .GetLargestContentfulPaintHandler()
               .MainFrameLargestContentfulPaint();
   // It's possible to get here and for LCP timing to not be available.
-  if (main_frame_largest_contentful_paint.ContainsValidTime()) {
-    PAGE_LOAD_HISTOGRAM(kNonTabWebUINavigationToLCPHistogramName,
-                        main_frame_largest_contentful_paint.Time().value());
-    PAGE_LOAD_HISTOGRAM(GetSuffixedLCPHistogram(webui_name_),
-                        main_frame_largest_contentful_paint.Time().value());
+  if (!main_frame_largest_contentful_paint.ContainsValidTime()) {
+    return;
   }
+  PAGE_LOAD_HISTOGRAM(kNonTabWebUINavigationToLCPHistogramName,
+                      main_frame_largest_contentful_paint.Time().value());
+  PAGE_LOAD_HISTOGRAM(GetSuffixedLCPHistogram(webui_name_),
+                      main_frame_largest_contentful_paint.Time().value());
+
+  // Time from request to LCP. This metric disregards time spent in the
+  // background, which is non-zero when the WebUI is preloaded.
+  base::TimeDelta background_time = GetBackgroundTime(GetDelegate());
+  PAGE_LOAD_SHORT_HISTOGRAM(
+      kNonTabWebUIRequestToLCPHistogramName,
+      main_frame_largest_contentful_paint.Time().value() - background_time);
+  PAGE_LOAD_SHORT_HISTOGRAM(
+      GetSuffixedRequestToLCPHistogram(webui_name_),
+      main_frame_largest_contentful_paint.Time().value() - background_time);
 }
 
 page_load_metrics::PageLoadMetricsObserver::ObservePolicy
diff --git a/chrome/browser/page_load_metrics/observers/non_tab_webui_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/non_tab_webui_page_load_metrics_observer.h
index bb34555..b9199fa 100644
--- a/chrome/browser/page_load_metrics/observers/non_tab_webui_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/non_tab_webui_page_load_metrics_observer.h
@@ -8,6 +8,7 @@
 #include "components/page_load_metrics/browser/page_load_metrics_observer.h"
 
 extern const char kNonTabWebUIRequestToFCPHistogramName[];
+extern const char kNonTabWebUIRequestToLCPHistogramName[];
 
 // Records Page Load Metrics for non-tab chrome:// pages such as side-panel
 // content and webUI based bubbles. This covers any webUI that goes through
diff --git a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
index 13dc437..11548a83 100644
--- a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
+++ b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
@@ -37,7 +37,6 @@
 #include "components/performance_manager/public/features.h"
 #include "components/performance_manager/public/graph/graph.h"
 #include "components/performance_manager/public/metrics/page_resource_monitor.h"
-#include "components/performance_manager/public/scenarios/loading_scenario_observer.h"
 #include "components/performance_manager/public/scenarios/performance_scenarios.h"
 #include "components/performance_manager/public/user_tuning/tab_revisit_tracker.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -115,8 +114,6 @@
 void ChromeBrowserMainExtraPartsPerformanceManager::CreatePoliciesAndDecorators(
     performance_manager::Graph* graph) {
   graph->PassToGraph(
-      std::make_unique<performance_manager::LoadingScenarioObserver>());
-  graph->PassToGraph(
       std::make_unique<performance_manager::ProcessMetricsDecorator>());
   graph->PassToGraph(
       std::make_unique<performance_manager::PageLiveStateDecorator>());
@@ -220,9 +217,14 @@
 }
 
 void ChromeBrowserMainExtraPartsPerformanceManager::PostCreateThreads() {
+  auto graph_features = performance_manager::GraphFeatures::WithDefault();
+  if (base::FeatureList::IsEnabled(
+          performance_manager::features::kLoadingPerformanceScenario)) {
+    graph_features.EnableLoadingScenario();
+  }
   performance_manager_lifetime_ =
       std::make_unique<performance_manager::PerformanceManagerLifetime>(
-          performance_manager::GraphFeatures::WithDefault(),
+          graph_features,
           base::BindOnce(&ChromeBrowserMainExtraPartsPerformanceManager::
                              CreatePoliciesAndDecorators));
 
diff --git a/chrome/browser/platform_experience/win b/chrome/browser/platform_experience/win
index f8bbf7f..6b3b72a 160000
--- a/chrome/browser/platform_experience/win
+++ b/chrome/browser/platform_experience/win
@@ -1 +1 @@
-Subproject commit f8bbf7f750c82f7463cebabcaee82182b709859f
+Subproject commit 6b3b72a0536bca7d403ed0439ddf9f434e076191
diff --git a/chrome/browser/resources/.eslintrc.js b/chrome/browser/resources/.eslintrc.js
deleted file mode 100644
index 4b88951..0000000
--- a/chrome/browser/resources/.eslintrc.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  'env' : {
-    'browser' : true,
-    'es6' : true,
-  },
-  'rules' : {
-    'eqeqeq' : ['error', 'always', {'null' : 'ignore'}],
-  },
-};
diff --git a/chrome/browser/resources/ash/settings/.eslintrc.js b/chrome/browser/resources/ash/settings/.eslintrc.js
deleted file mode 100644
index 7bbc50e3..0000000
--- a/chrome/browser/resources/ash/settings/.eslintrc.js
+++ /dev/null
@@ -1,50 +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.
-
-/**
- * @fileoverview
- * CrOS Settings SWA specific ESLint rules.
- */
-
-module.exports = {
-  // Disable clang-format because it produces odd formatting for these rules.
-  // clang-format off
-  rules: {
-    // Disable due to large number of violations in this folder.
-    '@typescript-eslint/consistent-type-imports': 'off',
-    /**
-     * https://google.github.io/styleguide/tsguide.html#return-types
-     * The Google TS style guide makes no formal rule on enforcing explicit
-     * return types. However, explicit return types have clear advantages in
-     * both readability and maintainability.
-     */
-    '@typescript-eslint/explicit-function-return-type': [
-      'error',
-      {
-        // Function expressions are exempt.
-        allowExpressions: true,
-        // Avoid checking Polymer static getter methods.
-        allowedNames: ['is', 'template', 'properties', 'observers'],
-      },
-    ],
-    /**
-     * https://google.github.io/styleguide/tsguide.html#type-inference
-     */
-    '@typescript-eslint/no-inferrable-types': [
-      'error',
-      {
-        // Function parameters may have explicit types for clearer APIs.
-        ignoreParameters: true,
-        // Class properties may have explicit types for clearer APIs.
-        ignoreProperties: true,
-      },
-    ],
-    /**
-     * https://google.github.io/styleguide/tsguide.html#function-expressions
-     */
-    'prefer-arrow-callback': 'error',
-    'quote-props': ['error', 'consistent-as-needed'],
-  },
-  // clang-format on
-};
diff --git a/chrome/browser/resources/chromeos/.eslintrc.js b/chrome/browser/resources/chromeos/.eslintrc.js
deleted file mode 100644
index ff82a7495..0000000
--- a/chrome/browser/resources/chromeos/.eslintrc.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  'ignorePatterns' : ['network_ui/third_party/uPlot.iife.min.d.ts'],
-  'rules' : {
-    // Turn off since there are too many imports of 'Polymer'. Remove if/when
-    // everything under this folder is migrated to PolymerElement.
-    'no-restricted-imports' : 'off',
-
-    // Turn off since there are many violations in this folder.
-    '@typescript-eslint/consistent-type-imports' : 'off',
-
-    // Turn off until violations in this folder are fixed.
-    '@typescript-eslint/ban-ts-comment' : 'off',
-  },
-};
diff --git a/chrome/browser/resources/chromeos/accessibility/.eslintrc.js b/chrome/browser/resources/chromeos/accessibility/.eslintrc.js
deleted file mode 100644
index 8bb56f9..0000000
--- a/chrome/browser/resources/chromeos/accessibility/.eslintrc.js
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  'rules' : {
-    'arrow-spacing' : ['error'],
-    'brace-style' : ['error', '1tbs'],
-    'curly' : ['error', 'multi-line', 'consistent'],
-    'eqeqeq' : ['error', 'always', {'null' : 'ignore'}],
-    'no-confusing-arrow' : ['error'],
-    'no-console' : 'off',
-    'no-throw-literal' : 'off',
-    'no-var' : 'off',
-    'object-shorthand' : ['error', 'always'],
-    'prefer-const' : ['error', {'destructuring' : 'all'}],
-    '@typescript-eslint/explicit-function-return-type' : 'off',
-    '@typescript-eslint/naming-convention' : 'off',
-  },
-  // clang-format off
-  'overrides':
-    [
-      {
-        // enable the rule specifically for TypeScript files
-        'files': ['*.ts'],
-        'rules': {
-          '@typescript-eslint/explicit-function-return-type':
-            [
-              'error',
-              {
-                'allowExpressions': true,
-              },
-            ],
-            // https://google.github.io/styleguide/jsguide.html#naming
-            '@typescript-eslint/naming-convention': [
-              'error',
-              {
-                selector: ['class', 'interface', 'typeAlias', 'enum', 'typeParameter'],
-                format: ['StrictPascalCase'],
-                filter: {
-                  regex: '^(' +
-                      // Exclude TypeScript defined interfaces HTMLElementTagNameMap
-                      // and HTMLElementEventMap.
-                      'HTMLElementTagNameMap|HTMLElementEventMap|' +
-                      // Exclude native DOM types which are always named like HTML<Foo>Element.
-                      'HTML[A-Za-z]{0,}Element|' +
-                      // Exclude native DOM interfaces.
-                      'UIEvent|UIEventInit|DOMError|' +
-                      // Exclude ISearch.
-                      'ISearch|ISearchHandler|ISearchUI|' +
-                      // Exclude the SA* classes.
-                      'SACache|SACommands|SAChildNode|SANode|SARootNode)$',
-                  match: false,
-                },
-              },
-              {
-                selector: 'enumMember',
-                format: ['UPPER_CASE'],
-              },
-              {
-                selector: 'classMethod',
-                format: ['strictCamelCase'],
-                modifiers: ['public'],
-                filter: {
-                  regex: '^(' +
-                      // Exclude initialisms such as JSON and IME
-                      'toJSON|describeTextChangedByIME|' +
-                      // Exclude the short name CVox
-                      'isCVoxModifierActive|' +
-                      // Exclude the phrase OS.
-                      'addOSKeyboardShortcutsMenuItem' +
-                      ')$',
-                  match: false,
-                },
-              },
-              {
-                selector: 'classMethod',
-                format: ['strictCamelCase'],
-                modifiers: ['private'],
-                trailingUnderscore: 'allow',
-                filter: {
-                  regex: '^(' +
-                  // Exclude ISearch and ITutorial.
-                  'createNewISearch_|destroyISearch_|setRangeToISearchNode_|' +
-                  'createITutorial_' +
-                  ')$',
-                  match: false,
-                },
-              },
-              {
-                selector: 'classProperty',
-                format: ['UPPER_CASE'],
-                modifiers: ['private', 'static', 'readonly'],
-              },
-              {
-                selector: 'classProperty',
-                format: ['UPPER_CASE'],
-                modifiers: ['public', 'static', 'readonly'],
-              },
-              {
-                selector: 'classProperty',
-                format: ['camelCase'],
-                modifiers: ['public'],
-              },
-              {
-                selector: 'classProperty',
-                format: ['camelCase'],
-                modifiers: ['private'],
-                trailingUnderscore: 'allow',
-              },
-              {
-                selector: 'parameter',
-                format: ['camelCase'],
-                leadingUnderscore: 'allow',
-              },
-              {
-                selector: 'function',
-                format: ['camelCase'],
-              },
-            ],
-        },
-      },
-    ],
-  // clang-format on
-};
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/.eslintrc.js b/chrome/browser/resources/chromeos/accessibility/chromevox/.eslintrc.js
deleted file mode 100644
index e15ad99..0000000
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/.eslintrc.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  'rules': {
-    // Override restrictions for document.getElementById usage since,
-    // chrome://resources/ash/common/util.js is not accessible for chromevox.
-    'no-restricted-properties': 'off',
-  },
-};
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/.eslintrc.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/.eslintrc.js
deleted file mode 100644
index 98f658b..0000000
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/.eslintrc.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  'rules': {
-    // Override restrictions for document.getElementById usage since,
-    // chrome://resources/ash/common/util.js is not accessible for select_to_speak.
-    'no-restricted-properties': 'off',
-  },
-};
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/.eslintrc.js b/chrome/browser/resources/chromeos/accessibility/switch_access/.eslintrc.js
deleted file mode 100644
index 7d67a73..0000000
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/.eslintrc.js
+++ /dev/null
@@ -1,11 +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.
-
-module.exports = {
-  'rules': {
-    // Override restrictions for document.getElementById usage since
-    // chrome://resources/ash/common/util.js is not accessible for switch_access.
-    'no-restricted-properties': 'off',
-  },
-};
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/.eslintrc.js b/chrome/browser/resources/chromeos/multidevice_internals/.eslintrc.js
deleted file mode 100644
index 78c346c5..0000000
--- a/chrome/browser/resources/chromeos/multidevice_internals/.eslintrc.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  'env': {
-    'browser': true,
-    'es6': true,
-  },
-  'rules': {'eqeqeq': ['error', 'always', {'null': 'ignore'}]},
-};
diff --git a/chrome/browser/resources/chromeos/nearby_internals/.eslintrc.js b/chrome/browser/resources/chromeos/nearby_internals/.eslintrc.js
deleted file mode 100644
index 57952d7e..0000000
--- a/chrome/browser/resources/chromeos/nearby_internals/.eslintrc.js
+++ /dev/null
@@ -1,11 +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.
-
-module.exports = {
-  'rules' : {
-    // Remove if/when everything under this folder is migrated to
-    // PolymerElement.
-    'no-restricted-imports': 'off',
-  },
-};
diff --git a/chrome/browser/resources/lens/overlay/translate_button.html b/chrome/browser/resources/lens/overlay/translate_button.html
index 5347f277..544a41d 100644
--- a/chrome/browser/resources/lens/overlay/translate_button.html
+++ b/chrome/browser/resources/lens/overlay/translate_button.html
@@ -397,8 +397,9 @@
   <div id="languagePicker">
     <cr-button id="sourceLanguageButton" class="button language-picker-button"
         on-click="onSourceLanguageButtonClick"
-        aria-hidden="[[!isTranslateModeEnabled]]"
-        tabindex$="[[getTabIndexForLanguagePicker(isTranslateModeEnabled)]]">
+        aria-hidden="[[!languagePickerButtonsVisible]]"
+        tabindex$="[[getTabIndexForLanguagePickerButtons(isTranslateModeEnabled,
+                    sourceLanguageMenuVisible, targetLanguageMenuVisible)]]">
       <span id="starsIcon" slot="prefix-icon"></span>
       <span id="sourceLanguageLabel"
           class="button-label language-picker-label">
@@ -409,8 +410,9 @@
     <span id="arrowRightIcon"></span>
     <cr-button id="targetLanguageButton" class="button language-picker-button"
         on-click="onTargetLanguageButtonClick"
-        aria-hidden="[[!isTranslateModeEnabled]]"
-        tabindex$="[[getTabIndexForLanguagePicker(isTranslateModeEnabled)]]">
+        aria-hidden="[[!languagePickerButtonsVisible]]"
+        tabindex$="[[getTabIndexForLanguagePickerButtons(isTranslateModeEnabled,
+                    sourceLanguageMenuVisible, targetLanguageMenuVisible)]]">
       <span id="targetLanguageLabel" class="button-label language-picker-label">
         [[getTargetLanguageDisplayName(targetLanguage)]]
       </span>
@@ -419,17 +421,18 @@
   <div id="translateDisableButtonContainer">
     <cr-button id="translateDisableButton" class="button"
       on-click="onTranslateButtonClick"
-      aria-hidden="[[!isTranslateModeEnabled]]"
-      tabindex$="[[getTabIndexForLanguagePicker(isTranslateModeEnabled)]]">
+      aria-hidden="[[!languagePickerButtonsVisible]]"
+      tabindex$="[[getTabIndexForLanguagePickerButtons(isTranslateModeEnabled,
+                  sourceLanguageMenuVisible, targetLanguageMenuVisible)]]">
     </cr-button>
     <span id="translateDisableIcon" class="translate-icon"></span>
   </div>
   <div id="sourceLanguagePickerMenu" class="language-picker-menu" role="menu"
       aria-labelledby="sourceLanguageButton">
     <div class="language-picker-menu-title-section">
-      <cr-icon-button iron-icon="cr:arrow-back"
-          class="language-picker-menu-back-button" role="button"
-          aria-label="$i18n{backButton}" title="$i18n{backButton}"
+      <cr-icon-button id="sourceLanguagePickerBackButton"
+          iron-icon="cr:arrow-back" class="language-picker-menu-back-button"
+          role="button" aria-label="$i18n{backButton}" title="$i18n{backButton}"
           on-click="hideLanguagePickerMenus"></cr-icon-button>
       <div class="language-picker-menu-title">$i18n{translateFrom}</div>
     </div>
@@ -471,9 +474,9 @@
   <div id="targetLanguagePickerMenu" class="language-picker-menu" role="menu"
       aria-labelledby="targetLanguageButton">
     <div class="language-picker-menu-title-section">
-      <cr-icon-button iron-icon="cr:arrow-back"
-          class="language-picker-menu-back-button" role="button"
-          aria-label="$i18n{backButton}" title="$i18n{backButton}"
+      <cr-icon-button id="targetLanguagePickerBackButton"
+          iron-icon="cr:arrow-back" class="language-picker-menu-back-button"
+          role="button" aria-label="$i18n{backButton}" title="$i18n{backButton}"
           on-click="hideLanguagePickerMenus"></cr-icon-button>
       <div class="language-picker-menu-title">$i18n{translateTo}</div>
     </div>
diff --git a/chrome/browser/resources/lens/overlay/translate_button.ts b/chrome/browser/resources/lens/overlay/translate_button.ts
index 248f8861..2a4b2e7 100644
--- a/chrome/browser/resources/lens/overlay/translate_button.ts
+++ b/chrome/browser/resources/lens/overlay/translate_button.ts
@@ -8,11 +8,12 @@
 import '//resources/cr_elements/icons_lit.html.js';
 
 import type {CrButtonElement} from '//resources/cr_elements/cr_button/cr_button.js';
+import type {CrIconButtonElement} from '//resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import {assert, assertInstanceof} from '//resources/js/assert.js';
 import {EventTracker} from '//resources/js/event_tracker.js';
 import {loadTimeData} from '//resources/js/load_time_data.js';
 import type {DomRepeat} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {afterNextRender, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import type {BrowserProxy} from './browser_proxy.js';
 import {BrowserProxyImpl} from './browser_proxy.js';
@@ -50,9 +51,11 @@
     languagePicker: HTMLDivElement,
     sourceAutoDetectButton: CrButtonElement,
     sourceLanguageButton: CrButtonElement,
+    sourceLanguagePickerBackButton: CrIconButtonElement,
     sourceLanguagePickerContainer: DomRepeat,
     sourceLanguagePickerMenu: HTMLDivElement,
     targetLanguageButton: CrButtonElement,
+    targetLanguagePickerBackButton: CrIconButtonElement,
     targetLanguagePickerContainer: DomRepeat,
     targetLanguagePickerMenu: HTMLDivElement,
     translateDisableButton: CrButtonElement,
@@ -79,6 +82,12 @@
         type: Boolean,
         reflectToAttribute: true,
       },
+      languagePickerButtonsVisible: {
+        type: Boolean,
+        computed: `computeLanguagePickerButtonsVisible(
+              isTranslateModeEnabled, sourceLanguageMenuVisible,
+              targetLanguageMenuVisible)`,
+      },
       shouldShowStarsIcon: {
         type: Boolean,
         computed: 'computeShouldShowStarsIcon(sourceLanguage)',
@@ -100,6 +109,8 @@
   private eventTracker_: EventTracker = new EventTracker();
   // Whether the translate mode on the lens overlay has been enabled.
   private isTranslateModeEnabled: boolean = false;
+  // Whether the language picker buttons are currently visible.
+  private languagePickerButtonsVisible: boolean;
   // Whether the stars icon is visible on the source language button.
   private shouldShowStarsIcon: boolean;
   // The currently selected source language to translate to. If null, we should
@@ -196,11 +207,19 @@
   private onSourceLanguageButtonClick() {
     this.sourceLanguageMenuVisible = !this.sourceLanguageMenuVisible;
     this.targetLanguageMenuVisible = false;
+    // We need to wait for the language picker to render before focusing.
+    afterNextRender(this, () => {
+      this.$.sourceLanguagePickerBackButton.focus();
+    });
   }
 
   private onTargetLanguageButtonClick() {
     this.targetLanguageMenuVisible = !this.targetLanguageMenuVisible;
     this.sourceLanguageMenuVisible = false;
+    // We need to wait for the language picker to render before focusing.
+    afterNextRender(this, () => {
+      this.$.targetLanguagePickerBackButton.focus();
+    });
   }
 
   private onSourceLanguageMenuItemClick(event: PointerEvent) {
@@ -255,8 +274,10 @@
       focusShimmerOnRegion(
           this, /*top=*/ 0, /*left=*/ 0, /*width=*/ 0, /*height=*/ 0,
           ShimmerControlRequester.TRANSLATE);
+      this.$.sourceLanguageButton.focus();
     } else {
       unfocusShimmer(this, ShimmerControlRequester.TRANSLATE);
+      this.$.translateEnableButton.focus();
     }
 
     // Dispatch event to let other components know the overlay translate mode
@@ -346,6 +367,14 @@
   private hideLanguagePickerMenus() {
     this.$.sourceLanguagePickerMenu.scroll(0, 0);
     this.$.targetLanguagePickerMenu.scroll(0, 0);
+    // Depending on which language picker menu was opened, return focus to
+    // the corresponding language button.
+    if (this.sourceLanguageMenuVisible) {
+      this.$.sourceLanguageButton.focus();
+    } else {
+      this.$.targetLanguageButton.focus();
+    }
+
     this.targetLanguageMenuVisible = false;
     this.sourceLanguageMenuVisible = false;
   }
@@ -399,8 +428,13 @@
     return this.isTranslateModeEnabled ? -1 : 0;
   }
 
-  private getTabIndexForLanguagePicker(): number {
-    return this.isTranslateModeEnabled ? 0 : -1;
+  private getTabIndexForLanguagePickerButtons(): number {
+    return this.computeLanguagePickerButtonsVisible() ? 0 : -1;
+  }
+
+  private computeLanguagePickerButtonsVisible(): boolean {
+    return this.isTranslateModeEnabled && !this.sourceLanguageMenuVisible &&
+        !this.targetLanguageMenuVisible;
   }
 
   private getAutoCheckedClass(
diff --git a/chrome/browser/resources/new_tab_page/app.ts b/chrome/browser/resources/new_tab_page/app.ts
index 8f0c757..c35421f 100644
--- a/chrome/browser/resources/new_tab_page/app.ts
+++ b/chrome/browser/resources/new_tab_page/app.ts
@@ -652,7 +652,7 @@
       case NtpWallpaperSearchButtonHideCondition.BACKGROUND_IMAGE_SET:
         return !this.showBackgroundImage_;
       case NtpWallpaperSearchButtonHideCondition.THEME_SET:
-        return !this.showBackgroundImage_ && !this.backgroundColor_;
+        return this.colorSourceIsBaseline;
     }
     return false;
   }
diff --git a/chrome/browser/resources/web_app_internals/web_app_internals.ts b/chrome/browser/resources/web_app_internals/web_app_internals.ts
index 1a751bf..59c9ce4 100644
--- a/chrome/browser/resources/web_app_internals/web_app_internals.ts
+++ b/chrome/browser/resources/web_app_internals/web_app_internals.ts
@@ -376,40 +376,37 @@
       const updateBtn = document.createElement('button');
       updateBtn.className = 'iwa-dev-update-button';
 
-      if (location.updateManifestUrl) {
-        // TODO(crbug.com/369051617): Implement an update routine for this.
-        updateBtn.disabled = true;
-        updateBtn.innerText =
-            'Updates for manifest installations are not yet supported.';
-      } else {
-        updateBtn.innerText = 'Perform update now';
-        updateBtn.onclick = async () => {
-          const oldText = updateBtn.innerText;
-          try {
-            updateBtn.disabled = true;
-            updateBtn.innerText =
-                'Performing update... (close the IWA if it is currently open!)';
+      updateBtn.innerText = 'Perform update now';
+      updateBtn.onclick = async () => {
+        const oldText = updateBtn.innerText;
+        try {
+          updateBtn.disabled = true;
+          updateBtn.innerText =
+              'Performing update... (close the IWA if it is currently open!)';
 
-            if (location.bundlePath) {
-              const {result}: {result: string} =
-                  await webAppInternalsHandler
-                      .selectFileAndUpdateIsolatedWebAppFromDevBundle(appId);
-              updateMsg.innerText = result;
-            } else if (location.proxyOrigin) {
-              const {result}: {result: string} =
-                  await webAppInternalsHandler.updateDevProxyIsolatedWebApp(
-                      appId);
-              updateMsg.innerText = result;
-            } else {
-              // TODO(crbug.com/369051617): Handle location.updateManifestUrl.
-              assertNotReached();
-            }
-          } finally {
-            updateBtn.innerText = oldText;
-            updateBtn.disabled = false;
+          if (location.bundlePath) {
+            const {result}: {result: string} =
+                await webAppInternalsHandler
+                    .selectFileAndUpdateIsolatedWebAppFromDevBundle(appId);
+            updateMsg.innerText = result;
+          } else if (location.proxyOrigin) {
+            const {result}: {result: string} =
+                await webAppInternalsHandler.updateDevProxyIsolatedWebApp(
+                    appId);
+            updateMsg.innerText = result;
+          } else if (location.updateManifestUrl) {
+            const {result}: {result: string} =
+                await webAppInternalsHandler
+                    .updateManifestInstalledIsolatedWebApp(appId);
+            updateMsg.innerText = result;
+          } else {
+            assertNotReached();
           }
-        };
-      }
+        } finally {
+          updateBtn.innerText = oldText;
+          updateBtn.disabled = false;
+        }
+      };
 
       li.appendChild(updateBtn);
       li.appendChild(updateMsg);
diff --git a/chrome/browser/tpcd/heuristics/opener_heuristic_browsertest.cc b/chrome/browser/tpcd/heuristics/opener_heuristic_browsertest.cc
index c249fd1b..ac13645 100644
--- a/chrome/browser/tpcd/heuristics/opener_heuristic_browsertest.cc
+++ b/chrome/browser/tpcd/heuristics/opener_heuristic_browsertest.cc
@@ -13,7 +13,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
-#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/dips/dips_storage.h"
 #include "chrome/browser/dips/dips_test_utils.h"
 #include "chrome/browser/dips/dips_utils.h"
diff --git a/chrome/browser/tpcd/heuristics/opener_heuristic_service.cc b/chrome/browser/tpcd/heuristics/opener_heuristic_service.cc
index 2a522c353..5322370f 100644
--- a/chrome/browser/tpcd/heuristics/opener_heuristic_service.cc
+++ b/chrome/browser/tpcd/heuristics/opener_heuristic_service.cc
@@ -5,7 +5,7 @@
 #include "chrome/browser/tpcd/heuristics/opener_heuristic_service.h"
 
 #include "chrome/browser/chrome_content_browser_client.h"
-#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/privacy_sandbox/tracking_protection_settings_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/tpcd/experiment/tpcd_experiment_features.h"
diff --git a/chrome/browser/tpcd/heuristics/opener_heuristic_tab_helper.cc b/chrome/browser/tpcd/heuristics/opener_heuristic_tab_helper.cc
index 517c069..3b0c5de 100644
--- a/chrome/browser/tpcd/heuristics/opener_heuristic_tab_helper.cc
+++ b/chrome/browser/tpcd/heuristics/opener_heuristic_tab_helper.cc
@@ -15,7 +15,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/dips/dips_bounce_detector.h"
-#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "chrome/browser/tpcd/experiment/tpcd_experiment_features.h"
 #include "chrome/browser/tpcd/heuristics/opener_heuristic_metrics.h"
diff --git a/chrome/browser/tpcd/heuristics/redirect_heuristic_tab_helper.cc b/chrome/browser/tpcd/heuristics/redirect_heuristic_tab_helper.cc
index e8edf9d7..54d482563 100644
--- a/chrome/browser/tpcd/heuristics/redirect_heuristic_tab_helper.cc
+++ b/chrome/browser/tpcd/heuristics/redirect_heuristic_tab_helper.cc
@@ -6,7 +6,7 @@
 
 #include "base/rand_util.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
-#include "chrome/browser/dips/dips_service_factory.h"
+#include "chrome/browser/dips/dips_service_impl.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/tpcd/experiment/tpcd_experiment_features.h"
 #include "chrome/browser/tpcd/heuristics/opener_heuristic_metrics.h"
diff --git a/chrome/browser/tpcd/metadata/devtools_observer.h b/chrome/browser/tpcd/metadata/devtools_observer.h
index 93196a5..d12eaf5 100644
--- a/chrome/browser/tpcd/metadata/devtools_observer.h
+++ b/chrome/browser/tpcd/metadata/devtools_observer.h
@@ -7,11 +7,14 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
-#include "chrome/browser/dips/dips_service.h"
-#include "chrome/browser/tpcd/heuristics/opener_heuristic_tab_helper.h"
 #include "content/public/browser/cookie_access_details.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace content_settings {
+class CookieSettings;
+}
 
 namespace content_settings {
 class CookieSettings;
@@ -49,7 +52,7 @@
       const content::CookieAccessDetails::Type cookie_access_type);
 
   scoped_refptr<content_settings::CookieSettings> cookie_settings_;
-  raw_ptr<tpcd::metadata::Manager> tpcd_metadata_manager_;
+  raw_ptr<Manager> tpcd_metadata_manager_;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 };
diff --git a/chrome/browser/tpcd/metadata/devtools_observer_browsertest.cc b/chrome/browser/tpcd/metadata/devtools_observer_browsertest.cc
index 46a1ee2..e3c3d04 100644
--- a/chrome/browser/tpcd/metadata/devtools_observer_browsertest.cc
+++ b/chrome/browser/tpcd/metadata/devtools_observer_browsertest.cc
@@ -8,7 +8,6 @@
 #include "base/values.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/dips/dips_service.h"
 #include "chrome/browser/dips/dips_test_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h"
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 93c60be..1d4df92c 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2355,6 +2355,7 @@
       "//chrome/browser/ui/ash/login",
       "//chrome/browser/ui/ash/login/login_screen_extension_ui",
       "//chrome/browser/ui/ash/main_extra_parts",
+      "//chrome/browser/ui/ash/management_disclosure",
       "//chrome/browser/ui/ash/media_client",
       "//chrome/browser/ui/ash/multi_user",
       "//chrome/browser/ui/ash/network",
@@ -2808,6 +2809,7 @@
       "//chrome/browser/ui/ash/in_session_auth",
       "//chrome/browser/ui/ash/login",
       "//chrome/browser/ui/ash/login/login_screen_extension_ui",
+      "//chrome/browser/ui/ash/management_disclosure",
       "//chrome/browser/ui/ash/media_client",
       "//chrome/browser/ui/ash/multi_user",
       "//chrome/browser/ui/ash/network",
diff --git a/chrome/browser/ui/android/edge_to_edge/internal/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerImpl.java b/chrome/browser/ui/android/edge_to_edge/internal/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerImpl.java
index b16fab06..70cb7054 100644
--- a/chrome/browser/ui/android/edge_to_edge/internal/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerImpl.java
+++ b/chrome/browser/ui/android/edge_to_edge/internal/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerImpl.java
@@ -5,6 +5,8 @@
 package org.chromium.chrome.browser.ui.edge_to_edge;
 
 import android.app.Activity;
+import android.content.ComponentCallbacks;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Build.VERSION_CODES;
 import android.view.View;
@@ -65,6 +67,7 @@
     private final Callback<LayoutManager> mOnLayoutManagerCallback =
             new ValueChangedCallback<>(this::updateLayoutStateProvider);
     private final FullscreenManager mFullscreenManager;
+    private final ComponentCallbacks mComponentCallback;
 
     // Cached rects used for adding under fullscreen.
     private final Rect mCachedWindowVisibleRect = new Rect();
@@ -169,6 +172,20 @@
         mWindowInsetsConsumer = this::handleWindowInsets;
         mInsetObserver.addInsetsConsumer(mWindowInsetsConsumer);
 
+        mComponentCallback =
+                new ComponentCallbacks() {
+                    @Override
+                    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+                        // When configuration changed, force an padding update.
+                        // See https://crbug.com/369887909
+                        drawToEdge(mIsPageOptedIntoEdgeToEdge, /* changedWindowState= */ true);
+                    }
+
+                    @Override
+                    public void onLowMemory() {}
+                };
+        mActivity.registerComponentCallbacks(mComponentCallback);
+
         assert mInsetObserver.getLastRawWindowInsets() != null
                 : "The inset observer should have non-null insets by the time the"
                         + " EdgeToEdgeControllerImpl is initialized.";
@@ -471,6 +488,11 @@
             topPadding = Math.max(0, mCachedWindowVisibleRect.top - mCachedContentVisibleRect.top);
             bottomPadding =
                     Math.max(0, mCachedContentVisibleRect.bottom - mCachedWindowVisibleRect.bottom);
+        } else if (topPadding == 0) {
+            // In odd cases, we see Chrome has set a 0 as top padding observed in
+            // crbug.com/369887909. In those cases, fix the padding based on the visible area.
+            Log.w(TAG, "topPadding = 0 when not in fullscreen mode.");
+            topPadding = Math.abs(mCachedWindowVisibleRect.top - mCachedContentVisibleRect.top);
         }
 
         // Use Insets to store the paddings as it is immutable.
@@ -499,6 +521,7 @@
     @CallSuper
     @Override
     public void destroy() {
+        mActivity.unregisterComponentCallbacks(mComponentCallback);
         if (mWebContentsObserver != null) {
             mWebContentsObserver.destroy();
             mWebContentsObserver = null;
diff --git a/chrome/browser/ui/ash/BUILD.gn b/chrome/browser/ui/ash/BUILD.gn
index 7ff22c4..9a50608 100644
--- a/chrome/browser/ui/ash/BUILD.gn
+++ b/chrome/browser/ui/ash/BUILD.gn
@@ -129,6 +129,7 @@
     "//chrome/browser/ui/ash/login",
     "//chrome/browser/ui/ash/login/login_screen_extension_ui",
     "//chrome/browser/ui/ash/main_extra_parts",
+    "//chrome/browser/ui/ash/management_disclosure",
     "//chrome/browser/ui/ash/media_client",
     "//chrome/browser/ui/ash/multi_user",
     "//chrome/browser/ui/ash/network",
diff --git a/chrome/browser/ui/ash/birch/birch_coral_provider_browsertest.cc b/chrome/browser/ui/ash/birch/birch_coral_provider_browsertest.cc
index b90e7d9d..d1a671c 100644
--- a/chrome/browser/ui/ash/birch/birch_coral_provider_browsertest.cc
+++ b/chrome/browser/ui/ash/birch/birch_coral_provider_browsertest.cc
@@ -30,7 +30,11 @@
 
 class BirchCoralProviderTest : public extensions::PlatformAppBrowserTest {
  public:
-  BirchCoralProviderTest() = default;
+  BirchCoralProviderTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {features::kBirchCoral, features::kCoralFeature}, {});
+  }
+
   BirchCoralProviderTest(const BirchCoralProviderTest&) = delete;
   BirchCoralProviderTest& operator=(const BirchCoralProviderTest&) = delete;
   ~BirchCoralProviderTest() override = default;
@@ -54,7 +58,7 @@
   }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_{features::kBirchCoral};
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Tests that the coral provider collects correct in-session tab and app data.
diff --git a/chrome/browser/ui/ash/main_extra_parts/BUILD.gn b/chrome/browser/ui/ash/main_extra_parts/BUILD.gn
index fd8b44b..0a1dcf77 100644
--- a/chrome/browser/ui/ash/main_extra_parts/BUILD.gn
+++ b/chrome/browser/ui/ash/main_extra_parts/BUILD.gn
@@ -31,6 +31,7 @@
     "//chrome/browser:browser_process",
     "//chrome/browser:browser_public_dependencies",
     "//chrome/browser/ash/app_list",
+    "//chrome/browser/ash/app_restore:app_restore",
     "//chrome/browser/ash/arc/util",
     "//chrome/browser/ash/boca",
     "//chrome/browser/ash/geolocation",
@@ -42,6 +43,7 @@
     "//chrome/browser/ash/mahi",
     "//chrome/browser/ash/mahi/media_app",
     "//chrome/browser/ash/net",
+    "//chrome/browser/ash/policy/core",
     "//chrome/browser/ash/policy/display",
     "//chrome/browser/ash/privacy_hub",
     "//chrome/browser/ash/profiles",
@@ -60,6 +62,7 @@
     "//chrome/browser/ui/ash/in_session_auth",
     "//chrome/browser/ui/ash/input_method",
     "//chrome/browser/ui/ash/login",
+    "//chrome/browser/ui/ash/management_disclosure",
     "//chrome/browser/ui/ash/media_client",
     "//chrome/browser/ui/ash/network",
     "//chrome/browser/ui/ash/new_window",
@@ -78,6 +81,7 @@
     "//chrome/browser/ui/views/select_file_dialog_extension",
     "//chrome/browser/ui/webui/ash/settings:settings",
     "//chromeos/ash/components/boca",
+    "//chromeos/ash/components/browser_context_helper",
     "//chromeos/ash/components/dbus",
     "//chromeos/ash/components/dbus/resourced",
     "//chromeos/ash/components/game_mode",
diff --git a/chrome/browser/ui/ash/main_extra_parts/DEPS b/chrome/browser/ui/ash/main_extra_parts/DEPS
index 085292e..9371038 100644
--- a/chrome/browser/ui/ash/main_extra_parts/DEPS
+++ b/chrome/browser/ui/ash/main_extra_parts/DEPS
@@ -11,6 +11,7 @@
   # refactor ash codes in //chrome to break these dependencies; see b/332804822.
   # Whenever possible, avoid adding new //chrome dependencies to this list.
   "+chrome/browser/ash/app_list",
+  "+chrome/browser/ash/app_restore",
   "+chrome/browser/ash/arc/util",
   "+chrome/browser/ash/auth",
   "+chrome/browser/ash/boca",
@@ -23,6 +24,7 @@
   "+chrome/browser/ash/magic_boost",
   "+chrome/browser/ash/mahi",
   "+chrome/browser/ash/net",
+  "+chrome/browser/ash/policy/core/browser_policy_connector_ash.h",
   "+chrome/browser/ash/policy/display",
   "+chrome/browser/ash/privacy_hub",
   "+chrome/browser/ash/profiles",
@@ -45,6 +47,7 @@
   "+chrome/browser/ui/ash/input_method",
   "+chrome/browser/ui/ash/in_session_auth",
   "+chrome/browser/ui/ash/login",
+  "+chrome/browser/ui/ash/management_disclosure",
   "+chrome/browser/ui/ash/media_client",
   "+chrome/browser/ui/ash/network",
   "+chrome/browser/ui/ash/new_window",
diff --git a/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc
index aaf490dd..ec9f413 100644
--- a/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc
@@ -31,6 +31,7 @@
 #include "base/scoped_observation.h"
 #include "base/trace_event/trace_event.h"
 #include "chrome/browser/ash/app_list/app_list_client_impl.h"
+#include "chrome/browser/ash/app_restore/full_restore_service.h"
 #include "chrome/browser/ash/arc/util/arc_window_watcher.h"
 #include "chrome/browser/ash/auth/active_session_fingerprint_client_impl.h"
 #include "chrome/browser/ash/boca/boca_app_client_impl.h"
@@ -44,6 +45,7 @@
 #include "chrome/browser/ash/mahi/media_app/mahi_media_app_content_manager_impl.h"
 #include "chrome/browser/ash/mahi/media_app/mahi_media_app_events_proxy_impl.h"
 #include "chrome/browser/ash/net/vpn_list_forwarder.h"
+#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/display/display_resolution_handler.h"
 #include "chrome/browser/ash/policy/display/display_rotation_default_handler.h"
 #include "chrome/browser/ash/policy/display/display_settings_handler.h"
@@ -70,6 +72,7 @@
 #include "chrome/browser/ui/ash/input_method/ime_controller_client_impl.h"
 #include "chrome/browser/ui/ash/login/login_screen_client_impl.h"
 #include "chrome/browser/ui/ash/login/oobe_dialog_util_impl.h"
+#include "chrome/browser/ui/ash/management_disclosure/management_disclosure_client_impl.h"
 #include "chrome/browser/ui/ash/media_client/media_client_impl.h"
 #include "chrome/browser/ui/ash/network/mobile_data_notifications.h"
 #include "chrome/browser/ui/ash/network/network_connect_delegate.h"
@@ -95,6 +98,7 @@
 #include "chrome/browser/ui/views/tabs/tab_scrubber_chromeos.h"
 #include "chrome/browser/ui/webui/ash/settings/pref_names.h"
 #include "chromeos/ash/components/boca/boca_role_util.h"
+#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
 #include "chromeos/ash/components/dbus/dbus_thread_manager.h"
 #include "chromeos/ash/components/dbus/resourced/resourced_client.h"
 #include "chromeos/ash/components/game_mode/game_mode_controller.h"
@@ -372,6 +376,12 @@
   }
 
   login_screen_client_ = std::make_unique<LoginScreenClientImpl>();
+  management_disclosure_client_ =
+      std::make_unique<ManagementDisclosureClientImpl>(
+          g_browser_process->platform_part()->browser_policy_connector_ash(),
+          Profile::FromBrowserContext(
+              ash::BrowserContextHelper::Get()->GetSigninBrowserContext())
+              ->GetOriginalProfile());
   // https://crbug.com/884127 ensuring that LoginScreenClientImpl is initialized
   // before using it InitializeDeviceDisablingManager.
   g_browser_process->platform_part()->InitializeDeviceDisablingManager();
@@ -463,6 +473,11 @@
       prefs->GetBoolean(ash::settings::prefs::kSanitizeCompleted)) {
     prefs->SetBoolean(ash::settings::prefs::kSanitizeCompleted, false);
     prefs->CommitPendingWrite();
+
+    // Blocks full restore UI from showing up. `FullRestoreService` object is
+    // not created yet, so we use this static function.
+    ash::full_restore::FullRestoreService::SetLastSessionSanitized();
+
     ash::SystemAppLaunchParams params;
     params.url = GURL(base::StrCat({ash::kChromeUISanitizeAppURL, "?done"}));
     params.launch_source = apps::LaunchSource::kUnknown;
@@ -511,6 +526,7 @@
   display_settings_handler_.reset();
   media_client_.reset();
   login_screen_client_.reset();
+  management_disclosure_client_.reset();
   graduation_manager_.reset();
 
   ash::privacy_hub_util::SetAppAccessNotifier(nullptr);
diff --git a/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.h b/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.h
index 4a7493e09..1494548 100644
--- a/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.h
+++ b/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.h
@@ -67,6 +67,7 @@
 class ImeControllerClientImpl;
 class InSessionAuthDialogClient;
 class LoginScreenClientImpl;
+class ManagementDisclosureClientImpl;
 class MediaClientImpl;
 class MobileDataNotifications;
 class NetworkConnectDelegate;
@@ -181,6 +182,7 @@
 
   // Initialized in PostProfileInit in all configs:
   std::unique_ptr<LoginScreenClientImpl> login_screen_client_;
+  std::unique_ptr<ManagementDisclosureClientImpl> management_disclosure_client_;
   std::unique_ptr<MediaClientImpl> media_client_;
   std::unique_ptr<AppAccessNotifier> app_access_notifier_;
   std::unique_ptr<policy::DisplaySettingsHandler> display_settings_handler_;
diff --git a/chrome/browser/ui/ash/management_disclosure/BUILD.gn b/chrome/browser/ui/ash/management_disclosure/BUILD.gn
index 2afd309..e3982bdb 100644
--- a/chrome/browser/ui/ash/management_disclosure/BUILD.gn
+++ b/chrome/browser/ui/ash/management_disclosure/BUILD.gn
@@ -12,8 +12,13 @@
     "management_disclosure_client_impl.h",
   ]
 
+  public_deps = [ "//chrome/browser:browser_public_dependencies" ]
+
   deps = [
     "//ash/public/cpp",
     "//base",
+    "//chrome/browser:browser_process",
+    "//chrome/browser/ash/policy/core",
+    "//chrome/browser/profiles:profile",
   ]
 }
diff --git a/chrome/browser/ui/ash/management_disclosure/DEPS b/chrome/browser/ui/ash/management_disclosure/DEPS
index 7a49cd5..f9ff0ccd 100644
--- a/chrome/browser/ui/ash/management_disclosure/DEPS
+++ b/chrome/browser/ui/ash/management_disclosure/DEPS
@@ -6,6 +6,11 @@
   # This directory is in //chrome, which violates the rule above. Allow this
   # directory to #include its own files.
   "+chrome/browser/ui/ash/management_disclosure",
+  "+chrome/browser/ash/policy/core/browser_policy_connector_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/ui/webui/management/management_ui_handler_chromeos.h",
+  "+chrome/browser/ui/webui/management/management_ui.h",
+  "+chrome/browser/profiles/profile.h",
 
   # Existing dependencies within //chrome. There is an active effort to
   # refactor ash codes in //chrome to break these dependencies; see b/332804822.
diff --git a/chrome/browser/ui/ash/management_disclosure/management_disclosure_client_impl.cc b/chrome/browser/ui/ash/management_disclosure/management_disclosure_client_impl.cc
index 5d5efc0..88d681b 100644
--- a/chrome/browser/ui/ash/management_disclosure/management_disclosure_client_impl.cc
+++ b/chrome/browser/ui/ash/management_disclosure/management_disclosure_client_impl.cc
@@ -4,14 +4,28 @@
 
 #include "chrome/browser/ui/ash/management_disclosure/management_disclosure_client_impl.h"
 
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
 #include "ash/public/cpp/login_screen.h"
+#include "base/check.h"
+#include "chrome/browser/browser_process_platform_part.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/management/management_ui.h"
+#include "chrome/browser/ui/webui/management/management_ui_handler_chromeos.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/webui/web_ui_util.h"
 
 namespace {
 ManagementDisclosureClientImpl* g_management_disclosure_client_instance =
     nullptr;
 }  // namespace
 
-ManagementDisclosureClientImpl::ManagementDisclosureClientImpl() {
+ManagementDisclosureClientImpl::ManagementDisclosureClientImpl(
+    policy::BrowserPolicyConnectorAsh* connector,
+    Profile* profile)
+    : connector_(connector), profile_(profile) {
   // Register this object as the client interface implementation.
   ash::LoginScreen::Get()->SetManagementDisclosureClient(this);
 
@@ -20,11 +34,41 @@
 }
 
 ManagementDisclosureClientImpl::~ManagementDisclosureClientImpl() {
-  // Register this object as the client interface implementation.
   ash::LoginScreen::Get()->SetManagementDisclosureClient(nullptr);
 
   DCHECK_EQ(this, g_management_disclosure_client_instance);
   g_management_disclosure_client_instance = nullptr;
+  connector_ = nullptr;
+  profile_ = nullptr;
 }
 
-void ManagementDisclosureClientImpl::SetVisible(bool visible) {}
+std::vector<std::u16string> ManagementDisclosureClientImpl::GetDisclosures() {
+  // Fill the map when it is first called.
+  if (policy_map_.empty()) {
+    std::vector<webui::LocalizedString> localized_strings;
+    ManagementUI::GetLocalizedStrings(localized_strings);
+    for (auto pair : localized_strings) {
+      policy_map_.insert(std::make_pair(pair.name, pair.id));
+    }
+  }
+
+  auto disclosures = ManagementUIHandlerChromeOS::GetDeviceReportingInfo(
+      connector_->GetDeviceCloudPolicyManager(), profile_);
+  std::vector<std::u16string> disclosure_list;
+  // Add all disclosures to list.
+  for (auto& disclosure : disclosures) {
+    if (disclosure.is_dict()) {
+      auto* message = disclosure.GetDict().Find("messageId");
+      if (message && message->is_string()) {
+        if (policy_map_.contains(message->GetString())) {
+          disclosure_list.push_back(l10n_util::GetStringUTF16(
+              policy_map_.find(message->GetString())->second));
+        } else {
+          LOG(WARNING) << "policy disclosure not found in policy_map";
+        }
+      }
+    }
+  }
+
+  return disclosure_list;
+}
diff --git a/chrome/browser/ui/ash/management_disclosure/management_disclosure_client_impl.h b/chrome/browser/ui/ash/management_disclosure/management_disclosure_client_impl.h
index 5700e3b..3168f3f 100644
--- a/chrome/browser/ui/ash/management_disclosure/management_disclosure_client_impl.h
+++ b/chrome/browser/ui/ash/management_disclosure/management_disclosure_client_impl.h
@@ -5,14 +5,18 @@
 #ifndef CHROME_BROWSER_UI_ASH_MANAGEMENT_DISCLOSURE_MANAGEMENT_DISCLOSURE_CLIENT_IMPL_H_
 #define CHROME_BROWSER_UI_ASH_MANAGEMENT_DISCLOSURE_MANAGEMENT_DISCLOSURE_CLIENT_IMPL_H_
 
+#include <unordered_map>
+
 #include "ash/public/cpp/management_disclosure_client.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 
 // Handles showing the management disclosure calls from ash to chrome.
 class ManagementDisclosureClientImpl : public ash::ManagementDisclosureClient {
  public:
-  ManagementDisclosureClientImpl();
+  ManagementDisclosureClientImpl(policy::BrowserPolicyConnectorAsh* connector,
+                                 Profile* profile);
 
   ManagementDisclosureClientImpl(const ManagementDisclosureClientImpl&) =
       delete;
@@ -21,9 +25,13 @@
 
   ~ManagementDisclosureClientImpl() override;
 
-  void SetVisible(bool visible) override;
+  std::vector<std::u16string> GetDisclosures() override;
 
  private:
+  raw_ptr<policy::BrowserPolicyConnectorAsh> connector_;
+  raw_ptr<Profile> profile_;
+  std::unordered_map<std::string, int> policy_map_;
+
   base::WeakPtrFactory<ManagementDisclosureClientImpl> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ui/extensions/mv2_disabled_dialog_controller.cc b/chrome/browser/ui/extensions/mv2_disabled_dialog_controller.cc
index 247e65e6..9e2912c 100644
--- a/chrome/browser/ui/extensions/mv2_disabled_dialog_controller.cc
+++ b/chrome/browser/ui/extensions/mv2_disabled_dialog_controller.cc
@@ -15,7 +15,6 @@
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/extensions/extensions_dialogs.h"
 #include "chrome/browser/ui/startup/startup_browser_creator.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
 #include "extensions/browser/extension_icon_placeholder.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_system.h"
diff --git a/chrome/browser/ui/hats/BUILD.gn b/chrome/browser/ui/hats/BUILD.gn
index d633623..e20c9522 100644
--- a/chrome/browser/ui/hats/BUILD.gn
+++ b/chrome/browser/ui/hats/BUILD.gn
@@ -68,6 +68,7 @@
     "//components/content_settings/core/browser",
     "//components/content_settings/core/browser:cookie_settings",
     "//components/content_settings/core/common",
+    "//components/lens:features",
     "//components/metrics_services_manager",
     "//components/password_manager/core/common",
     "//components/permissions",
diff --git a/chrome/browser/ui/hats/survey_config.cc b/chrome/browser/ui/hats/survey_config.cc
index 1fc0e36..1340c66 100644
--- a/chrome/browser/ui/hats/survey_config.cc
+++ b/chrome/browser/ui/hats/survey_config.cc
@@ -11,6 +11,7 @@
 #include "base/strings/string_util.h"
 #include "chrome/common/chrome_features.h"
 #include "components/autofill/core/common/autofill_features.h"
+#include "components/lens/lens_features.h"
 #include "components/permissions/features.h"
 #include "components/permissions/permission_hats_trigger_helper.h"
 #include "components/privacy_sandbox/privacy_sandbox_features.h"
@@ -52,6 +53,7 @@
     "download-warning-page-heed";
 constexpr char kHatsSurveyTriggerDownloadWarningPageIgnore[] =
     "download-warning-page-ignore";
+constexpr char kHatsSurveyTriggerLensOverlayResults[] = "lens-overlay-results";
 constexpr char kHatsSurveyTriggerM1AdPrivacyPage[] = "m1-ad-privacy-page";
 constexpr char kHatsSurveyTriggerM1TopicsSubpage[] = "m1-topics-subpage";
 constexpr char kHatsSurveyTriggerM1FledgeSubpage[] = "m1-fledge-subpage";
@@ -607,6 +609,12 @@
           .Get(),
       sh_psd_fields);
 
+  // Lens overlay surveys.
+  survey_configs.emplace_back(
+      &lens::features::kLensOverlaySurvey, kHatsSurveyTriggerLensOverlayResults,
+      /*presupplied_trigger_id=*/std::nullopt, std::vector<std::string>{},
+      std::vector<std::string>{"Paella ID"});
+
 #else
   survey_configs.emplace_back(&chrome::android::kChromeSurveyNextAndroid,
                               kHatsSurveyTriggerAndroidStartupSurvey);
diff --git a/chrome/browser/ui/hats/survey_config.h b/chrome/browser/ui/hats/survey_config.h
index c9b843a..753b99d6 100644
--- a/chrome/browser/ui/hats/survey_config.h
+++ b/chrome/browser/ui/hats/survey_config.h
@@ -32,6 +32,7 @@
 extern const char kHatsSurveyTriggerDownloadWarningPageBypass[];
 extern const char kHatsSurveyTriggerDownloadWarningPageHeed[];
 extern const char kHatsSurveyTriggerDownloadWarningPageIgnore[];
+extern const char kHatsSurveyTriggerLensOverlayResults[];
 extern const char kHatsSurveyTriggerM1AdPrivacyPage[];
 extern const char kHatsSurveyTriggerM1TopicsSubpage[];
 extern const char kHatsSurveyTriggerM1FledgeSubpage[];
diff --git a/chrome/browser/ui/lens/BUILD.gn b/chrome/browser/ui/lens/BUILD.gn
index a057e94..190dd25 100644
--- a/chrome/browser/ui/lens/BUILD.gn
+++ b/chrome/browser/ui/lens/BUILD.gn
@@ -98,6 +98,7 @@
     "//chrome/browser/ui/browser_window",
     "//chrome/browser/ui/color:color_headers",
     "//chrome/browser/ui/exclusive_access",
+    "//chrome/browser/ui/hats",
     "//chrome/browser/ui/views/bubble",
     "//chrome/browser/ui/views/side_panel",
     "//chrome/browser/ui/views/toolbar",
@@ -176,6 +177,8 @@
     "//chrome/browser/ui/browser_window",
     "//chrome/browser/ui/exclusive_access",
     "//chrome/browser/ui/find_bar",
+    "//chrome/browser/ui/hats",
+    "//chrome/browser/ui/hats:test_support",
     "//chrome/browser/ui/tabs:tabs_public",
     "//chrome/browser/ui/views/side_panel",
     "//chrome/test:test_support",
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.cc b/chrome/browser/ui/lens/lens_overlay_controller.cc
index f4371cb..e3166ba 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller.cc
@@ -24,6 +24,8 @@
 #include "chrome/browser/ui/browser_window/public/browser_window_features.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
+#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/lens/lens_overlay_controller_glue.h"
 #include "chrome/browser/ui/lens/lens_overlay_entry_point_controller.h"
 #include "chrome/browser/ui/lens/lens_overlay_event_handler.h"
@@ -438,6 +440,7 @@
   search_performed_in_session_ = false;
   invocation_time_ = base::TimeTicks::Now();
   invocation_time_since_epoch_ = base::Time::Now();
+  hats_triggered_in_session_ = false;
 }
 
 void LensOverlayController::CloseUIAsync(
@@ -964,6 +967,7 @@
 
   lens_overlay_query_controller_->SendFullPageTranslateQuery(source_language,
                                                              target_language);
+  MaybeLaunchSurvey();
 }
 
 void LensOverlayController::IssueEndTranslateModeRequest() {
@@ -2047,6 +2051,7 @@
   RecordTimeToFirstInteraction();
   search_performed_in_session_ = true;
   state_ = State::kOverlayAndResults;
+  MaybeLaunchSurvey();
 }
 
 void LensOverlayController::ActivityRequestedByOverlay(
@@ -2228,6 +2233,7 @@
   RecordTimeToFirstInteraction();
   search_performed_in_session_ = true;
   state_ = State::kOverlayAndResults;
+  MaybeLaunchSurvey();
 }
 
 void LensOverlayController::CloseSearchBubble() {
@@ -2304,6 +2310,7 @@
   CloseSearchBubble();
   RecordTimeToFirstInteraction();
   search_performed_in_session_ = true;
+  MaybeLaunchSurvey();
 
   // If we are in the zero state, this request must have come from CSB. In that
   // case, hide the overlay to allow live page to show through.
@@ -2406,3 +2413,24 @@
                                    search_performed_in_session_,
                                    session_duration);
 }
+
+void LensOverlayController::MaybeLaunchSurvey() {
+  if (!base::FeatureList::IsEnabled(lens::features::kLensOverlaySurvey)) {
+    return;
+  }
+  if (hats_triggered_in_session_) {
+    return;
+  }
+  hats_triggered_in_session_ = true;
+  HatsService* hats_service = HatsServiceFactory::GetForProfile(
+      tab_->GetBrowserWindowInterface()->GetProfile(),
+      /*create_if_necessary=*/true);
+  CHECK(hats_service);
+  hats_service->LaunchDelayedSurveyForWebContents(
+      kHatsSurveyTriggerLensOverlayResults, tab_->GetContents(),
+      lens::features::GetLensOverlaySurveyResultsTime().InMilliseconds(),
+      /*product_specific_bits_data=*/{},
+      /*product_specific_string_data=*/
+      {{"Paella ID",
+        base::NumberToString(lens_overlay_query_controller_->gen204_id())}});
+}
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.h b/chrome/browser/ui/lens/lens_overlay_controller.h
index 332b716..1d22ecc9 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.h
+++ b/chrome/browser/ui/lens/lens_overlay_controller.h
@@ -862,6 +862,9 @@
   void RecordEndOfSessionMetrics(
       lens::LensOverlayDismissalSource dismissal_source);
 
+  // Launches the Lens overlay HaTS survey if eligible.
+  void MaybeLaunchSurvey();
+
   // Owns this class.
   raw_ptr<tabs::TabInterface> tab_;
 
@@ -972,7 +975,7 @@
   // Indicates whether a search has been performed in the current session. Used
   // to record success/abandonment rate, as defined by whether or not a search
   // was performed.
-  bool search_performed_in_session_{false};
+  bool search_performed_in_session_ = false;
 
   // The time at which the overlay was invoked. Used to compute timing metrics.
   base::TimeTicks invocation_time_;
@@ -981,6 +984,11 @@
   // timeToWebUIReady on the WebUI side.
   base::Time invocation_time_since_epoch_;
 
+  // Indicates whether a trigger for the HaTS survey has occurred in the current
+  // session. Note that a trigger does not mean the survey will actually be
+  // shown.
+  bool hats_triggered_in_session_ = false;
+
   // ---------------Browser window scoped state: START---------------------
   // State that is scoped to the browser window must be reset when the tab is
   // backgrounded, since the tab may move between browser windows.
diff --git a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
index 843bfca..ce4700a 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
@@ -38,6 +38,8 @@
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
+#include "chrome/browser/ui/hats/mock_hats_service.h"
 #include "chrome/browser/ui/lens/lens_overlay_colors.h"
 #include "chrome/browser/ui/lens/lens_overlay_controller_glue.h"
 #include "chrome/browser/ui/lens/lens_overlay_entry_point_controller.h"
@@ -109,6 +111,7 @@
 constexpr char kPdfDocument[] = "/pdf/test.pdf";
 constexpr char kDocumentWithNonAsciiCharacters[] = "/non-ascii.html";
 
+using ::testing::_;
 using State = LensOverlayController::State;
 using LensOverlayInvocationSource = lens::LensOverlayInvocationSource;
 using LensOverlayDismissalSource = lens::LensOverlayDismissalSource;
@@ -587,6 +590,10 @@
     // Permits sharing the page screenshot by default.
     PrefService* prefs = browser()->profile()->GetPrefs();
     prefs->SetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled, true);
+
+    mock_hats_service_ = static_cast<MockHatsService*>(
+        HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+            browser()->profile(), base::BindRepeating(&BuildMockHatsService)));
   }
 
   void TearDownOnMainThread() override {
@@ -596,6 +603,8 @@
     // Disallow sharing the page screenshot by default.
     PrefService* prefs = browser()->profile()->GetPrefs();
     prefs->SetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled, false);
+
+    mock_hats_service_ = nullptr;
   }
 
   ~LensOverlayControllerBrowserTest() override {
@@ -614,7 +623,8 @@
           {
               {"use-inner-text-as-context", "true"},
               {"use-inner-html-as-context", "true"},
-          }}},
+          }},
+         {lens::features::kLensOverlaySurvey, {}}},
         /*disabled_features=*/{});
   }
 
@@ -764,6 +774,7 @@
 
  protected:
   base::test::ScopedFeatureList feature_list_;
+  raw_ptr<MockHatsService> mock_hats_service_ = nullptr;
 };
 
 IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
@@ -1049,6 +1060,9 @@
 
 IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
                        ShowSidePanelWithPendingRegion) {
+  EXPECT_CALL(*mock_hats_service_, LaunchDelayedSurveyForWebContents(
+                                       kHatsSurveyTriggerLensOverlayResults, _,
+                                       _, _, _, _, _, _, _, _));
   WaitForPaint();
 
   // State should start in off.
@@ -1189,6 +1203,9 @@
 #endif
 IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
                        MAYBE_SidePanelInteractionsAfterRegionSelection) {
+  EXPECT_CALL(*mock_hats_service_, LaunchDelayedSurveyForWebContents(
+                                       kHatsSurveyTriggerLensOverlayResults, _,
+                                       _, _, _, _, _, _, _, _));
   WaitForPaint();
 
   std::string text_query = "Apples";
@@ -1280,6 +1297,9 @@
 #endif
 IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
                        MAYBE_ShowSidePanelAfterTextSelectionRequest) {
+  EXPECT_CALL(*mock_hats_service_, LaunchDelayedSurveyForWebContents(
+                                       kHatsSurveyTriggerLensOverlayResults, _,
+                                       _, _, _, _, _, _, _, _));
   WaitForPaint();
 
   std::string text_query = "Apples";
@@ -1328,6 +1348,9 @@
 #endif
 IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
                        MAYBE_ShowSidePanelAfterTranslateSelectionRequest) {
+  EXPECT_CALL(*mock_hats_service_, LaunchDelayedSurveyForWebContents(
+                                       kHatsSurveyTriggerLensOverlayResults, _,
+                                       _, _, _, _, _, _, _, _));
   WaitForPaint();
 
   std::string text_query = "Manzanas";
@@ -1371,6 +1394,9 @@
 
 IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
                        IssueTranslateFullPageRequest) {
+  EXPECT_CALL(*mock_hats_service_, LaunchDelayedSurveyForWebContents(
+                                       kHatsSurveyTriggerLensOverlayResults, _,
+                                       _, _, _, _, _, _, _, _));
   WaitForPaint();
 
   // State should start in off.
diff --git a/chrome/browser/ui/lens/lens_overlay_query_controller.h b/chrome/browser/ui/lens/lens_overlay_query_controller.h
index c9a53703..a5deede3 100644
--- a/chrome/browser/ui/lens/lens_overlay_query_controller.h
+++ b/chrome/browser/ui/lens/lens_overlay_query_controller.h
@@ -130,6 +130,8 @@
   virtual void SendTaskCompletionGen204IfEnabled(
       lens::mojom::UserAction user_action);
 
+  uint64_t gen204_id() const { return gen204_id_; }
+
  protected:
   // Creates an endpoint fetcher for fetching the request data and fetches
   // the request.
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index 732705a..e00fd65 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -28,8 +28,6 @@
 #include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/content_settings/sound_content_setting_observer.h"
 #include "chrome/browser/dips/dips_bounce_detector.h"
-#include "chrome/browser/dips/dips_service.h"
-#include "chrome/browser/dips/stateful_bounce_counter.h"
 #include "chrome/browser/external_protocol/external_protocol_observer.h"
 #include "chrome/browser/favicon/favicon_utils.h"
 #include "chrome/browser/file_system_access/file_system_access_features.h"
@@ -411,9 +409,6 @@
   ConnectionHelpTabHelper::CreateForWebContents(web_contents);
   CoreTabHelper::CreateForWebContents(web_contents);
   DIPSWebContentsObserver::MaybeCreateForWebContents(web_contents);
-  if (auto* dips_wco = DIPSWebContentsObserver::FromWebContents(web_contents)) {
-    CHECK(dips::StatefulBounceCounter::Get(dips_wco));
-  }
 #if BUILDFLAG(ENABLE_REPORTING)
   if (base::FeatureList::IsEnabled(
           net::features::kReportingApiEnableEnterpriseCookieIssues)) {
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_browsertest.cc b/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_browsertest.cc
index 259a3390..0780cb9 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_browsertest.cc
+++ b/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_browsertest.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h"
 #include "chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_desktop.h"
 #include "chrome/browser/ui/tabs/tab_group_model.h"
+#include "chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
@@ -42,6 +43,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/views/view_utils.h"
 #include "url/gurl.h"
 
 namespace tab_groups {
@@ -53,7 +55,7 @@
     features_.InitWithFeatures(
         {tab_groups::kTabGroupsSaveV2,
          tab_groups::kTabGroupSyncServiceDesktopMigration},
-        {});
+        {tab_groups::kTabGroupsSaveUIUpdate});
   }
 
   void OnWillBeDestroyed() override {
@@ -156,6 +158,9 @@
   service->AddObserver(this);
 
   SavedTabGroup group(u"Title", tab_groups::TabGroupColorId::kBlue, {}, 0);
+  SavedTabGroupTab tab1(GURL("about:blank"), u"about:blank", group.saved_guid(),
+                        /*position=*/0);
+  group.AddTabLocally(tab1);
   const base::Uuid sync_id = group.saved_guid();
   EXPECT_FALSE(service->GetGroup(sync_id));
 
@@ -169,4 +174,71 @@
   EXPECT_FALSE(service->GetGroup(sync_id)->local_group_id().has_value());
 }
 
+// Regression test. See crbug.com/370013915.
+IN_PROC_BROWSER_TEST_F(
+    TabGroupSyncDelegateBrowserTest,
+    GroupsWithIndicesOutsideLocalIndexRangeInsertedAtTheEnd) {
+  TabGroupSyncService* service =
+      TabGroupSyncServiceFactory::GetForProfile(browser()->profile());
+  service->AddObserver(this);
+
+  auto saved_tab_group_bar =
+      std::make_unique<SavedTabGroupBar>(browser(), service_, false);
+  EXPECT_EQ(1u, saved_tab_group_bar->children().size());
+
+  chrome::AddTabAt(browser(), GURL("chrome://newtab"), 0, false);
+  LocalTabGroupID local_id = browser()->tab_strip_model()->AddToNewGroup({0});
+  EXPECT_TRUE(
+      browser()->tab_strip_model()->group_model()->ContainsTabGroup(local_id));
+  std::optional<SavedTabGroup> group_1 = service->GetGroup(local_id);
+  EXPECT_TRUE(group_1);
+  EXPECT_EQ(2u, saved_tab_group_bar->children().size());
+
+  SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kPink, {}, 2);
+  SavedTabGroupTab tab2(GURL("about:blank"), u"about:blank",
+                        group_2.saved_guid(),
+                        /*position=*/0);
+  group_2.AddTabLocally(tab2);
+
+  SavedTabGroup group_3(u"Group 3", tab_groups::TabGroupColorId::kGreen, {},
+                        10);
+  SavedTabGroupTab tab3(GURL("about:blank"), u"about:blank",
+                        group_3.saved_guid(),
+                        /*position=*/0);
+  group_3.AddTabLocally(tab3);
+
+  const base::Uuid sync_id_1 = group_1->saved_guid();
+  const base::Uuid sync_id_2 = group_2.saved_guid();
+  const base::Uuid sync_id_3 = group_3.saved_guid();
+
+  // FromSync calls are asynchronous, so wait for the task to complete.
+  model_->AddedFromSync(std::move(group_3));
+  EXPECT_TRUE(base::test::RunUntil(
+      [&]() { return service->GetGroup(sync_id_3).has_value(); }));
+  EXPECT_EQ(3u, saved_tab_group_bar->children().size());
+
+  model_->AddedFromSync(std::move(group_2));
+  EXPECT_TRUE(base::test::RunUntil(
+      [&]() { return service->GetGroup(sync_id_2).has_value(); }));
+  EXPECT_EQ(4u, saved_tab_group_bar->children().size());
+
+  // Verify the ordering is group 1, group 2, group 3
+  EXPECT_TRUE(views::IsViewClass<SavedTabGroupButton>(
+      saved_tab_group_bar->children()[0]));
+  EXPECT_TRUE(views::IsViewClass<SavedTabGroupButton>(
+      saved_tab_group_bar->children()[1]));
+  EXPECT_TRUE(views::IsViewClass<SavedTabGroupButton>(
+      saved_tab_group_bar->children()[2]));
+
+  EXPECT_EQ(sync_id_1, views::AsViewClass<SavedTabGroupButton>(
+                           saved_tab_group_bar->children()[0])
+                           ->guid());
+  EXPECT_EQ(sync_id_2, views::AsViewClass<SavedTabGroupButton>(
+                           saved_tab_group_bar->children()[1])
+                           ->guid());
+  EXPECT_EQ(sync_id_3, views::AsViewClass<SavedTabGroupButton>(
+                           saved_tab_group_bar->children()[2])
+                           ->guid());
+}
+
 }  // namespace tab_groups
diff --git a/chrome/browser/ui/toasts/api/toast_specification.cc b/chrome/browser/ui/toasts/api/toast_specification.cc
index 73a28dd..ee68856 100644
--- a/chrome/browser/ui/toasts/api/toast_specification.cc
+++ b/chrome/browser/ui/toasts/api/toast_specification.cc
@@ -71,6 +71,12 @@
     CHECK(toast_specification_->has_close_button());
     CHECK(!toast_specification_->menu_model());
   }
+
+  // Toasts with a menu can't have a close button. If this behavior is needed,
+  // discuss with UX how to design this in a way that supports both.
+  if (toast_specification_->menu_model()) {
+    CHECK(!toast_specification_->has_close_button());
+  }
 }
 
 ToastSpecification::ToastSpecification(
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index 57fb7d5..278301a 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -224,11 +224,10 @@
   }
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING) && \
     (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX))
-  if (base::FeatureList::IsEnabled(features::kUpdateTextOptions)) {
     return l10n_util::GetStringUTF16(IDS_RELAUNCH_TO_UPDATE_ALT);
-  }
-#endif
+#else
   return l10n_util::GetStringUTF16(IDS_RELAUNCH_TO_UPDATE);
+#endif
 }
 
 // Returns the appropriate menu label for the IDC_INSTALL_PWA command if
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index 0c66beb..8c275cf 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -383,18 +383,6 @@
 }
 #endif
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-// Enables alternate update-related text to be displayed in browser app menu
-// button, menu item and confirmation dialog.
-BASE_FEATURE(kUpdateTextOptions,
-             "UpdateTextOptions",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-// Used to present different flavors of update strings in browser app menu
-// button.
-const base::FeatureParam<int> kUpdateTextOptionNumber{
-    &kUpdateTextOptions, "UpdateTextOptionNumber", 2};
-#endif
-
 // Enables enterprise profile badging for managed profiles on the toolbar avatar
 // and in the profile menu. On managed profiles, a "Work" or "School" label will
 // be used in the toolbar, and a building icon will be used  as a badge in the
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h
index 1c46163..9c7d1bab 100644
--- a/chrome/browser/ui/ui_features.h
+++ b/chrome/browser/ui/ui_features.h
@@ -262,11 +262,6 @@
 
 bool IsToolbarPinningEnabled();
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-BASE_DECLARE_FEATURE(kUpdateTextOptions);
-extern const base::FeatureParam<int> kUpdateTextOptionNumber;
-#endif
-
 BASE_DECLARE_FEATURE(kEnterpriseProfileBadging);
 BASE_DECLARE_FEATURE(kEnterpriseProfileBadgingPolicies);
 BASE_DECLARE_FEATURE(kEnterpriseUpdatedProfileCreationScreen);
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
index 2a8445ba..2b05c6cd 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
@@ -658,16 +658,18 @@
     return;
   }
 
-  // Check that the index is valid for buttons
-  DCHECK_LE(index, static_cast<int>(children().size()));
-
+  // Ensure the button is placed within the bounds of children(). The last
+  // button is always reserved for the everything / overflow menu.
+  int num_buttons = static_cast<int>(children().size());
+  int clamped_index =
+      index < num_buttons ? index : std::max(0, num_buttons - 1);
   views::View* view = AddChildViewAt(
       std::make_unique<SavedTabGroupButton>(
           group,
           base::BindRepeating(&SavedTabGroupBar::OnTabGroupButtonPressed,
                               base::Unretained(this), group.saved_guid()),
           browser_, animations_enabled_),
-      index);
+      clamped_index);
   if (group.saved_tabs().size() == 0) {
     view->SetVisible(false);
   }
diff --git a/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_bubble_view_pixel_test.cc b/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_bubble_view_pixel_test.cc
index eceab6e..56b8cca 100644
--- a/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_bubble_view_pixel_test.cc
+++ b/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_bubble_view_pixel_test.cc
@@ -8,7 +8,6 @@
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/dips/dips_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
diff --git a/chrome/browser/ui/views/toolbar/app_menu.cc b/chrome/browser/ui/views/toolbar/app_menu.cc
index 4771598..4553811 100644
--- a/chrome/browser/ui/views/toolbar/app_menu.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu.cc
@@ -353,8 +353,7 @@
 std::u16string GetUpgradeDialogSubstringText() {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING) && \
     (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX))
-  if (base::FeatureList::IsEnabled(features::kUpdateTextOptions) &&
-      !UpgradeDetector::GetInstance()->is_outdated_install() &&
+  if (!UpgradeDetector::GetInstance()->is_outdated_install() &&
       !UpgradeDetector::GetInstance()->is_outdated_install_no_au()) {
     {
       return {l10n_util::GetStringUTF16(IDS_RELAUNCH_TO_UPDATE_ALT_MINOR_TEXT)};
diff --git a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
index d23b1e2..a589bc6 100644
--- a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
+++ b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
@@ -197,17 +197,15 @@
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING) && \
     (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX))
     int message_id = IDS_APP_MENU_BUTTON_UPDATE;
-    if (base::FeatureList::IsEnabled(features::kUpdateTextOptions)) {
-      // Select an update text option randomly. Show this text in all browser
-      // windows.
-      static const int update_text_option = base::RandInt(1, 3);
-      if (update_text_option == 1) {
-        message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT1;
-      } else if (update_text_option == 2) {
-        message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT2;
-      } else {
-        message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT3;
-      }
+    // Select an update text option randomly. Show this text in all browser
+    // windows.
+    static const int update_text_option = base::RandInt(1, 3);
+    if (update_text_option == 1) {
+      message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT1;
+    } else if (update_text_option == 2) {
+      message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT2;
+    } else {
+      message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT3;
     }
     text = l10n_util::GetStringUTF16(message_id);
 #else
diff --git a/chrome/browser/ui/views/update_recommended_message_box.cc b/chrome/browser/ui/views/update_recommended_message_box.cc
index d6dcb0f..a8efa473 100644
--- a/chrome/browser/ui/views/update_recommended_message_box.cc
+++ b/chrome/browser/ui/views/update_recommended_message_box.cc
@@ -4,13 +4,12 @@
 
 #include "chrome/browser/ui/views/update_recommended_message_box.h"
 
-#include "base/feature_list.h"
+#include "build/branding_buildflags.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/browser_list.h"
-#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/grit/branded_strings.h"
 #include "components/constrained_window/constrained_window_views.h"
@@ -44,9 +43,7 @@
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING) && \
     (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX))
-  SetTitle(base::FeatureList::IsEnabled(features::kUpdateTextOptions)
-               ? IDS_UPDATE_RECOMMENDED_DIALOG_TITLE_ALT
-               : IDS_UPDATE_RECOMMENDED_DIALOG_TITLE);
+  SetTitle(IDS_UPDATE_RECOMMENDED_DIALOG_TITLE_ALT);
 #else
   SetTitle(IDS_UPDATE_RECOMMENDED_DIALOG_TITLE);
 #endif
@@ -57,10 +54,7 @@
 #elif BUILDFLAG(GOOGLE_CHROME_BRANDING) && \
     (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX))
   update_message = l10n_util::GetPluralStringFUTF16(
-      base::FeatureList::IsEnabled(features::kUpdateTextOptions)
-          ? IDS_UPDATE_RECOMMENDED_ALT
-          : IDS_UPDATE_RECOMMENDED,
-      BrowserList::GetIncognitoBrowserCount());
+      IDS_UPDATE_RECOMMENDED_ALT, BrowserList::GetIncognitoBrowserCount());
 #else
   update_message = l10n_util::GetPluralStringFUTF16(
       IDS_UPDATE_RECOMMENDED, BrowserList::GetIncognitoBrowserCount());
diff --git a/chrome/browser/ui/webui/certificate_manager/certificate_manager_handler.cc b/chrome/browser/ui/webui/certificate_manager/certificate_manager_handler.cc
index 9199ded0..d42408a 100644
--- a/chrome/browser/ui/webui/certificate_manager/certificate_manager_handler.cc
+++ b/chrome/browser/ui/webui/certificate_manager/certificate_manager_handler.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/ui/webui/certificate_manager/client_cert_sources.h"
 #include "chrome/browser/ui/webui/certificate_manager/enterprise_cert_sources.h"
 #include "chrome/browser/ui/webui/certificate_manager/platform_cert_sources.h"
+#include "chrome/common/pref_names.h"
 #include "content/public/browser/network_service_instance.h"
 #include "content/public/browser/web_contents.h"
 
@@ -195,6 +196,13 @@
 }
 #endif
 
+#if !BUILDFLAG(IS_CHROMEOS)
+void CertificateManagerPageHandler::SetIncludeSystemTrustStore(bool include) {
+  auto* prefs = profile_->GetPrefs();
+  prefs->SetBoolean(prefs::kCAPlatformIntegrationEnabled, include);
+}
+#endif
+
 void CertificateManagerPageHandler::CertSource::ImportCertificate(
     base::WeakPtr<content::WebContents> web_contents,
     CertificateManagerPageHandler::ImportCertificateCallback callback) {
diff --git a/chrome/browser/ui/webui/certificate_manager/certificate_manager_handler.h b/chrome/browser/ui/webui/certificate_manager/certificate_manager_handler.h
index 29f3d557..6b69596 100644
--- a/chrome/browser/ui/webui/certificate_manager/certificate_manager_handler.h
+++ b/chrome/browser/ui/webui/certificate_manager/certificate_manager_handler.h
@@ -83,6 +83,10 @@
   void ShowNativeManageCertificates() override;
 #endif
 
+#if !BUILDFLAG(IS_CHROMEOS)
+  void SetIncludeSystemTrustStore(bool include) override;
+#endif
+
  private:
   // Returns a reference to the CertSource object corresponding to `source`.
   // Will always return a valid object, or will fail with a CHECK if `source`
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 9d7b7f5..67099133 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -130,12 +130,8 @@
 #include "ash/webui/print_preview_cros/url_constants.h"
 #include "ash/webui/recorder_app_ui/url_constants.h"
 #include "ash/webui/vc_background_ui/url_constants.h"
-#include "chrome/browser/ash/app_mode/kiosk_controller.h"
 #include "chrome/browser/ash/extensions/url_constants.h"
 #include "chrome/browser/extensions/extension_keeplist_chromeos.h"
-#include "chromeos/ash/components/kiosk/vision/internals_page_processor.h"
-#include "chromeos/ash/components/kiosk/vision/webui/constants.h"
-#include "chromeos/ash/components/kiosk/vision/webui/ui_controller.h"
 #include "chromeos/ash/components/scalable_iph/scalable_iph_constants.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
@@ -253,19 +249,6 @@
 }
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-template <>
-WebUIController* NewWebUI<ash::kiosk_vision::UIController>(WebUI* web_ui,
-                                                           const GURL& url) {
-  return new ash::kiosk_vision::UIController(
-      web_ui, base::BindRepeating(webui::SetupWebUIDataSource),
-      base::BindRepeating([]() {
-        return ash::KioskController::Get()
-            .GetKioskVisionInternalsPageProcessor();
-      }));
-}
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 template <>
 WebUIController* NewWebUI<commerce::CommerceInternalsUI>(WebUI* web_ui,
                                                          const GURL& url) {
@@ -329,13 +312,6 @@
   if (url.host_piece() == commerce::kChromeUICommerceInternalsHost) {
     return &NewWebUI<commerce::CommerceInternalsUI>;
   }
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  if (url.host_piece() ==
-          ash::kiosk_vision::kChromeUIKioskVisionInternalsHost &&
-      ash::kiosk_vision::IsInternalsPageEnabled()) {
-    return &NewWebUI<ash::kiosk_vision::UIController>;
-  }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   if (url.host_piece() ==
       optimization_guide_internals::kChromeUIOptimizationGuideInternalsHost) {
     return &NewWebUI<OptimizationGuideInternalsUI>;
diff --git a/chrome/browser/ui/webui/management/management_ui.cc b/chrome/browser/ui/webui/management/management_ui.cc
index 68e78257..d13d603a 100644
--- a/chrome/browser/ui/webui/management/management_ui.cc
+++ b/chrome/browser/ui/webui/management/management_ui.cc
@@ -58,6 +58,85 @@
   source->AddString("pageSubtitle",
                     ManagementUI::GetManagementPageSubtitle(profile));
 
+  std::vector<webui::LocalizedString> localized_strings;
+  ManagementUI::GetLocalizedStrings(localized_strings);
+  source->AddLocalizedStrings(localized_strings);
+
+  source->SetDefaultResource(IDR_MANAGEMENT_MANAGEMENT_HTML);
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  source->AddString("managementDeviceLearnMoreUrl",
+                    chrome::kLearnMoreEnterpriseURL);
+  source->AddString("managementAccountLearnMoreUrl",
+                    chrome::kManagedUiLearnMoreUrl);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
+  const size_t dlp_events_count =
+      policy::DlpRulesManagerFactory::GetForPrimaryProfile() &&
+              policy::DlpRulesManagerFactory::GetForPrimaryProfile()
+                  ->GetReportingManager()
+          ? policy::DlpRulesManagerFactory::GetForPrimaryProfile()
+                ->GetReportingManager()
+                ->events_reported()
+          : 0;
+  source->AddString(kManagementReportDlpEvents,
+                    l10n_util::GetPluralStringFUTF16(
+                        IDS_MANAGEMENT_REPORT_DLP_EVENTS, dlp_events_count));
+  source->AddString("pluginVmDataCollection",
+                    l10n_util::GetStringFUTF16(
+                        IDS_MANAGEMENT_REPORT_PLUGIN_VM,
+                        l10n_util::GetStringUTF16(IDS_PLUGIN_VM_APP_NAME)));
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
+
+  webui::SetupWebUIDataSource(
+      source, base::make_span(kManagementResources, kManagementResourcesSize),
+      IDR_MANAGEMENT_MANAGEMENT_HTML);
+  return source;
+}
+
+}  // namespace
+
+// static
+base::RefCountedMemory* ManagementUI::GetFaviconResourceBytes(
+    ui::ResourceScaleFactor scale_factor) {
+  return ui::ResourceBundle::GetSharedInstance().LoadDataResourceBytesForScale(
+      IDR_MANAGEMENT_FAVICON, scale_factor);
+}
+
+// static
+std::u16string ManagementUI::GetManagementPageSubtitle(Profile* profile) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  policy::BrowserPolicyConnectorAsh* connector =
+      g_browser_process->platform_part()->browser_policy_connector_ash();
+  const auto device_type = ui::GetChromeOSDeviceTypeResourceId();
+  if (!connector->IsDeviceEnterpriseManaged() &&
+      !profile->GetProfilePolicyConnector()->IsManaged()) {
+    return l10n_util::GetStringFUTF16(IDS_MANAGEMENT_NOT_MANAGED_SUBTITLE,
+                                      l10n_util::GetStringUTF16(device_type));
+  }
+
+  std::string account_manager = connector->GetEnterpriseDomainManager();
+
+  if (account_manager.empty()) {
+    account_manager =
+        chrome::GetAccountManagerIdentity(profile).value_or(std::string());
+  }
+  if (account_manager.empty()) {
+    return l10n_util::GetStringFUTF16(IDS_MANAGEMENT_SUBTITLE_MANAGED,
+                                      l10n_util::GetStringUTF16(device_type));
+  }
+  return l10n_util::GetStringFUTF16(IDS_MANAGEMENT_SUBTITLE_MANAGED_BY,
+                                    l10n_util::GetStringUTF16(device_type),
+                                    base::UTF8ToUTF16(account_manager));
+#else   // BUILDFLAG(IS_CHROMEOS_ASH)
+  return chrome::GetManagementPageSubtitle(profile);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+}
+
+// static
+void ManagementUI::GetLocalizedStrings(
+    std::vector<webui::LocalizedString>& strings) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
 #if BUILDFLAG(IS_CHROMEOS)
       {"learnMore", IDS_LEARN_MORE},
@@ -189,75 +268,9 @@
       {kProfileReportingLearnMore, IDS_MANAGEMENT_PROFILE_REPORTING_LEARN_MORE},
   };
 
-  source->AddLocalizedStrings(kLocalizedStrings);
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  source->AddString("managementDeviceLearnMoreUrl",
-                    chrome::kLearnMoreEnterpriseURL);
-  source->AddString("managementAccountLearnMoreUrl",
-                    chrome::kManagedUiLearnMoreUrl);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
-#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
-  const size_t dlp_events_count =
-      policy::DlpRulesManagerFactory::GetForPrimaryProfile() &&
-              policy::DlpRulesManagerFactory::GetForPrimaryProfile()
-                  ->GetReportingManager()
-          ? policy::DlpRulesManagerFactory::GetForPrimaryProfile()
-                ->GetReportingManager()
-                ->events_reported()
-          : 0;
-  source->AddString(kManagementReportDlpEvents,
-                    l10n_util::GetPluralStringFUTF16(
-                        IDS_MANAGEMENT_REPORT_DLP_EVENTS, dlp_events_count));
-  source->AddString("pluginVmDataCollection",
-                    l10n_util::GetStringFUTF16(
-                        IDS_MANAGEMENT_REPORT_PLUGIN_VM,
-                        l10n_util::GetStringUTF16(IDS_PLUGIN_VM_APP_NAME)));
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
-
-  webui::SetupWebUIDataSource(
-      source, base::make_span(kManagementResources, kManagementResourcesSize),
-      IDR_MANAGEMENT_MANAGEMENT_HTML);
-  return source;
-}
-
-}  // namespace
-
-// static
-base::RefCountedMemory* ManagementUI::GetFaviconResourceBytes(
-    ui::ResourceScaleFactor scale_factor) {
-  return ui::ResourceBundle::GetSharedInstance().LoadDataResourceBytesForScale(
-      IDR_MANAGEMENT_FAVICON, scale_factor);
-}
-
-// static
-std::u16string ManagementUI::GetManagementPageSubtitle(Profile* profile) {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  policy::BrowserPolicyConnectorAsh* connector =
-      g_browser_process->platform_part()->browser_policy_connector_ash();
-  const auto device_type = ui::GetChromeOSDeviceTypeResourceId();
-  if (!connector->IsDeviceEnterpriseManaged() &&
-      !profile->GetProfilePolicyConnector()->IsManaged()) {
-    return l10n_util::GetStringFUTF16(IDS_MANAGEMENT_NOT_MANAGED_SUBTITLE,
-                                      l10n_util::GetStringUTF16(device_type));
+  for (auto i : kLocalizedStrings) {
+    strings.push_back(i);
   }
-
-  std::string account_manager = connector->GetEnterpriseDomainManager();
-
-  if (account_manager.empty())
-    account_manager =
-        chrome::GetAccountManagerIdentity(profile).value_or(std::string());
-  if (account_manager.empty()) {
-    return l10n_util::GetStringFUTF16(IDS_MANAGEMENT_SUBTITLE_MANAGED,
-                                      l10n_util::GetStringUTF16(device_type));
-  }
-  return l10n_util::GetStringFUTF16(IDS_MANAGEMENT_SUBTITLE_MANAGED_BY,
-                                    l10n_util::GetStringUTF16(device_type),
-                                    base::UTF8ToUTF16(account_manager));
-#else   // BUILDFLAG(IS_CHROMEOS_ASH)
-  return chrome::GetManagementPageSubtitle(profile);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 ManagementUI::ManagementUI(content::WebUI* web_ui) : WebUIController(web_ui) {
diff --git a/chrome/browser/ui/webui/management/management_ui.h b/chrome/browser/ui/webui/management/management_ui.h
index 2bef581..934f0fea 100644
--- a/chrome/browser/ui/webui/management/management_ui.h
+++ b/chrome/browser/ui/webui/management/management_ui.h
@@ -5,12 +5,15 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_MANAGEMENT_MANAGEMENT_UI_H_
 #define CHROME_BROWSER_UI_WEBUI_MANAGEMENT_MANAGEMENT_UI_H_
 
+#include <vector>
+
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/webui_url_constants.h"
 #include "content/public/browser/web_ui_controller.h"
 #include "content/public/browser/webui_config.h"
 #include "content/public/common/url_constants.h"
 #include "ui/base/resource/resource_scale_factor.h"
+#include "ui/base/webui/web_ui_util.h"
 
 namespace base {
 class RefCountedMemory;
@@ -43,6 +46,8 @@
       ui::ResourceScaleFactor scale_factor);
 
   static std::u16string GetManagementPageSubtitle(Profile* profile);
+
+  static void GetLocalizedStrings(std::vector<webui::LocalizedString>& strings);
 };
 
 #endif  // CHROME_BROWSER_UI_WEBUI_MANAGEMENT_MANAGEMENT_UI_H_
diff --git a/chrome/browser/ui/webui/top_chrome/webui_contents_preload_manager_browsertest.cc b/chrome/browser/ui/webui/top_chrome/webui_contents_preload_manager_browsertest.cc
index a84992c7..76a40438 100644
--- a/chrome/browser/ui/webui/top_chrome/webui_contents_preload_manager_browsertest.cc
+++ b/chrome/browser/ui/webui/top_chrome/webui_contents_preload_manager_browsertest.cc
@@ -28,14 +28,17 @@
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
 #include "content/public/test/scoped_web_ui_controller_factory_registration.h"
 #include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/test_utils.h"
 #include "content/public/test/url_loader_interceptor.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/controls/webview/webview.h"
 #include "ui/views/widget/widget.h"
 #include "url/gurl.h"
+#include "url/url_constants.h"
 
 using testing::_;
 using testing::Return;
@@ -268,14 +271,14 @@
 // The page load metrics test is flaky on LaCrOS because sometimes viz::Display
 // reports a negative frame latency that causes page load metrics to stop
 // propagation.
-#define MAYBE_RequestToFCP DISABLED_RequestToFCP
+#define MAYBE_RequestToFCPAndLCP DISABLED_RequestToFCPAndLCP
 #else
-#define MAYBE_RequestToFCP RequestToFCP
+#define MAYBE_RequestToFCPAndLCP RequestToFCPAndLCP
 #endif
 // Tests that the time from the WebUI is requested to when First Contentful
 // Paint (FCP) is recorded.
 IN_PROC_BROWSER_TEST_F(WebUIContentsPreloadManagerPageLoadMetricsTest,
-                       MAYBE_RequestToFCP) {
+                       MAYBE_RequestToFCPAndLCP) {
   // Serves the test origin with files from the test data folder.
   auto url_loader_interceptor =
       content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
@@ -283,13 +286,15 @@
 
   base::HistogramTester histogram_tester;
   histogram_tester.ExpectTotalCount(kNonTabWebUIRequestToFCPHistogramName, 0);
+  histogram_tester.ExpectTotalCount(kNonTabWebUIRequestToLCPHistogramName, 0);
 
   test_api().MaybePreloadForBrowserContext(browser()->profile());
   navigation_waiter()->Wait();
   ASSERT_TRUE(test_api().GetPreloadedURL().has_value());
 
-  // FCP is not recorded because the WebUI is not yet shown.
+  // FCP and LCP are not recorded because the WebUI is not yet shown.
   histogram_tester.ExpectTotalCount(kNonTabWebUIRequestToFCPHistogramName, 0);
+  histogram_tester.ExpectTotalCount(kNonTabWebUIRequestToLCPHistogramName, 0);
 
   WebUIContentsPreloadManager::RequestResult request_result =
       preload_manager()->Request(*test_api().GetPreloadedURL(),
@@ -314,6 +319,12 @@
 
   WaitForHistogram(kNonTabWebUIRequestToFCPHistogramName);
   histogram_tester.ExpectTotalCount(kNonTabWebUIRequestToFCPHistogramName, 1);
+  // LCP is not recorded until WebContents close or navigation.
+  histogram_tester.ExpectTotalCount(kNonTabWebUIRequestToLCPHistogramName, 0);
+
+  ASSERT_TRUE(content::NavigateToURL(web_contents, GURL(url::kAboutBlankURL)));
+  WaitForHistogram(kNonTabWebUIRequestToLCPHistogramName);
+  histogram_tester.ExpectTotalCount(kNonTabWebUIRequestToLCPHistogramName, 1);
 
   widget->CloseNow();
 }
diff --git a/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.cc b/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.cc
index f2f2c2f..2d9fcab 100644
--- a/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.cc
+++ b/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.cc
@@ -6,6 +6,7 @@
 
 #include "base/containers/to_vector.h"
 #include "base/functional/overloaded.h"
+#include "base/strings/stringprintf.h"
 #include "base/types/expected_macros.h"
 #include "chrome/browser/file_select_helper.h"
 #include "chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom.h"
@@ -14,6 +15,7 @@
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_features.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_install_command_helper.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_installation_manager.h"
+#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolation_data.h"
@@ -119,9 +121,124 @@
   base::OnceCallback<void(std::optional<base::FilePath>)> callback_;
 };
 
+class IwaInternalsHandler::IwaManifestInstallUpdateHandler
+    : public IsolatedWebAppUpdateManager::Observer {
+ public:
+  explicit IwaManifestInstallUpdateHandler(WebAppProvider& provider)
+      : provider_(provider) {
+    observation_.Observe(&provider.iwa_update_manager());
+  }
+
+  void UpdateManifestInstalledIsolatedWebApp(
+      const webapps::AppId& app_id,
+      Handler::UpdateManifestInstalledIsolatedWebAppCallback callback) {
+    if (base::Contains(update_requests_, app_id)) {
+      std::move(callback).Run(
+          "Update check skipped: please wait for the pending update request to "
+          "resolve first.");
+      return;
+    }
+
+    ASSIGN_OR_RETURN(
+        const WebApp& iwa,
+        GetIsolatedWebAppById(provider_->registrar_unsafe(), app_id),
+        [&](const std::string& error) { std::move(callback).Run(error); });
+
+    ASSIGN_OR_RETURN(
+        const IsolatedWebAppUrlInfo& url_info,
+        IsolatedWebAppUrlInfo::Create(iwa.manifest_id()),
+        [&](const std::string& error) { std::move(callback).Run(error); })
+
+    const IsolationData& isolation_data = *iwa.isolation_data();
+    if (!isolation_data.location().dev_mode() ||
+        !isolation_data.update_manifest_url()) {
+      std::move(callback).Run(
+          "Only dev-mode apps with update_manifest_url set can be updated via "
+          "this routine.");
+      return;
+    }
+
+    update_requests_.emplace(app_id, std::move(callback));
+    provider_->iwa_update_manager().DiscoverUpdatesForApp(
+        url_info, *isolation_data.update_manifest_url(), /*dev_mode=*/true);
+  }
+
+  // IsolatedWebAppUpdateManager::Observer:
+  void OnUpdateDiscoveryTaskCompleted(
+      const webapps::AppId& app_id,
+      IsolatedWebAppUpdateDiscoveryTask::CompletionStatus status) override {
+    if (status.has_value() && *status ==
+                                  IsolatedWebAppUpdateDiscoveryTask::Success::
+                                      kUpdateFoundAndSavedInDatabase) {
+      return;
+    }
+
+    ASSIGN_OR_RETURN(auto callback, ConsumeUpdateRequest(app_id), [](auto) {});
+    if (status.has_value()) {
+      std::move(callback).Run(
+          "Update skipped: app is already on the latest version.");
+    } else {
+      std::move(callback).Run(
+          "Update failed: " +
+          IsolatedWebAppUpdateDiscoveryTask::ErrorToString(status.error()));
+    }
+  }
+
+  void OnUpdateApplyTaskCompleted(
+      const webapps::AppId& app_id,
+      IsolatedWebAppUpdateApplyTask::CompletionStatus status) override {
+    ASSIGN_OR_RETURN(auto callback, ConsumeUpdateRequest(app_id), [](auto) {});
+
+    ASSIGN_OR_RETURN(
+        const WebApp& iwa,
+        GetIsolatedWebAppById(provider_->registrar_unsafe(), app_id),
+        [&](const std::string& error) { std::move(callback).Run(error); });
+
+    std::move(callback).Run(
+        status.has_value()
+            ? base::StringPrintf("Update to v%s successful (refresh the page "
+                                 "to reflect the update).",
+                                 iwa.isolation_data()->version().GetString())
+            : "Update failed: " + status.error().message);
+  }
+
+ private:
+  // Retrieves the pending request for `app_id` and erases the entry from the
+  // requests map. If there's no matching entry for `app_id`, returns an
+  // unexpected.
+  base::expected<Handler::UpdateDevProxyIsolatedWebAppCallback, absl::monostate>
+  ConsumeUpdateRequest(const webapps::AppId& app_id) {
+    auto itr = update_requests_.find(app_id);
+    if (itr == update_requests_.end()) {
+      return base::unexpected(absl::monostate());
+    }
+
+    auto callback = std::move(itr->second);
+    update_requests_.erase(itr);
+
+    return std::move(callback);
+  }
+
+  const raw_ref<WebAppProvider> provider_;
+
+  base::ScopedObservation<IsolatedWebAppUpdateManager,
+                          IwaManifestInstallUpdateHandler>
+      observation_{this};
+
+  // Multiple parallel requests for one `app_id` are not allowed.
+  base::flat_map<webapps::AppId,
+                 Handler::UpdateManifestInstalledIsolatedWebAppCallback>
+      update_requests_;
+};
+
 IwaInternalsHandler::IwaInternalsHandler(content::WebUI& web_ui,
                                          Profile& profile)
-    : web_ui_(web_ui), profile_(profile) {}
+    : web_ui_(web_ui), profile_(profile) {
+  if (auto* provider = WebAppProvider::GetForWebApps(&profile)) {
+    update_handler_ =
+        std::make_unique<IwaManifestInstallUpdateHandler>(*provider);
+  }
+}
 
 IwaInternalsHandler::~IwaInternalsHandler() = default;
 
@@ -411,6 +528,18 @@
       base::PassKey<IwaInternalsHandler>(), web_bundle_id, public_key);
 }
 
+void IwaInternalsHandler::UpdateManifestInstalledIsolatedWebApp(
+    const webapps::AppId& app_id,
+    Handler::UpdateManifestInstalledIsolatedWebAppCallback callback) {
+  if (!update_handler_) {
+    std::move(callback).Run(
+        "WebAppProvider is not available for the current profile.");
+    return;
+  }
+  update_handler_->UpdateManifestInstalledIsolatedWebApp(app_id,
+                                                         std::move(callback));
+}
+
 void IwaInternalsHandler::DownloadWebBundleToFile(
     const GURL& web_bundle_url,
     const GURL& update_manifest_url,
diff --git a/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.h b/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.h
index ff6b4d5..2df298a 100644
--- a/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.h
+++ b/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.h
@@ -70,8 +70,13 @@
   void RotateKey(const std::string& web_bundle_id,
                  const std::optional<std::vector<uint8_t>>& public_key);
 
+  void UpdateManifestInstalledIsolatedWebApp(
+      const webapps::AppId& app_id,
+      Handler::UpdateManifestInstalledIsolatedWebAppCallback callback);
+
  private:
   class IsolatedWebAppDevBundleSelectListener;
+  class IwaManifestInstallUpdateHandler;
   friend class web_app::WebAppInternalsIwaInstallationBrowserTest;
 
   Profile* profile() { return &profile_.get(); }
@@ -119,6 +124,11 @@
   const raw_ref<content::WebUI> web_ui_;
   const raw_ref<Profile> profile_;
 
+  // Runs updates for manifest-installed dev-mode apps.
+  // Will be nullptr if WebAppProvider is not available for the current
+  // `profile_`.
+  std::unique_ptr<IwaManifestInstallUpdateHandler> update_handler_;
+
   base::WeakPtrFactory<IwaInternalsHandler> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom b/chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom
index 651def57..7bd2a9b3 100644
--- a/chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom
+++ b/chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom
@@ -88,6 +88,9 @@
   // success or error message.
   SelectFileAndUpdateIsolatedWebAppFromDevBundle(string app_id) =>
       (string result);
+  // Triggers an update for a manifest-installed dev mode app.
+  // Returns a string containing a success or error message.
+  UpdateManifestInstalledIsolatedWebApp(string app_id) => (string result);
 
   // Triggers update discovery for installed non-dev-mode Isolated Web Apps.
   // Returns a string containing a success or error message.
diff --git a/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.cc b/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.cc
index 5e2ec51..aac67712 100644
--- a/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.cc
+++ b/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.cc
@@ -558,3 +558,10 @@
     const std::optional<std::vector<uint8_t>>& public_key) {
   iwa_handler_.RotateKey(web_bundle_id, public_key);
 }
+
+void WebAppInternalsHandler::UpdateManifestInstalledIsolatedWebApp(
+    const webapps::AppId& app_id,
+    UpdateManifestInstalledIsolatedWebAppCallback callback) {
+  iwa_handler_.UpdateManifestInstalledIsolatedWebApp(app_id,
+                                                     std::move(callback));
+}
diff --git a/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.h b/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.h
index 26a243c..97ca6d6e 100644
--- a/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.h
+++ b/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.h
@@ -67,6 +67,9 @@
   void RotateKey(
       const std::string& web_bundle_id,
       const std::optional<std::vector<uint8_t>>& public_key) override;
+  void UpdateManifestInstalledIsolatedWebApp(
+      const webapps::AppId& app_id,
+      UpdateManifestInstalledIsolatedWebAppCallback callback) override;
 
  private:
   const raw_ref<content::WebUI> web_ui_;
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task.cc b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task.cc
index 6e1d661b..b49c333 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_downloader.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_install_command_helper.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_prepare_and_store_update_command.h"
+#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_source.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
 #include "chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest.h"
 #include "chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher.h"
@@ -135,21 +136,20 @@
 }
 
 IsolatedWebAppUpdateDiscoveryTask::IsolatedWebAppUpdateDiscoveryTask(
-    GURL update_manifest_url,
-    IsolatedWebAppUrlInfo url_info,
+    IwaUpdateDiscoveryTaskParams task_params,
     WebAppCommandScheduler& command_scheduler,
     WebAppRegistrar& registrar,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
-    : update_manifest_url_(std::move(update_manifest_url)),
-      url_info_(std::move(url_info)),
+    : task_params_(std::move(task_params)),
       command_scheduler_(command_scheduler),
       registrar_(registrar),
       url_loader_factory_(std::move(url_loader_factory)) {
   CHECK(url_loader_factory_);
-  debug_log_ = base::Value::Dict()
-                   .Set("bundle_id", url_info_.web_bundle_id().id())
-                   .Set("app_id", url_info_.app_id())
-                   .Set("update_manifest_url", update_manifest_url_.spec());
+  debug_log_ =
+      base::Value::Dict()
+          .Set("bundle_id", task_params_.url_info.web_bundle_id().id())
+          .Set("app_id", task_params_.url_info.app_id())
+          .Set("update_manifest_url", task_params_.update_manifest_url.spec());
 }
 
 IsolatedWebAppUpdateDiscoveryTask::~IsolatedWebAppUpdateDiscoveryTask() =
@@ -163,7 +163,7 @@
   debug_log_.Set("start_time", base::TimeToValue(base::Time::Now()));
 
   update_manifest_fetcher_ = std::make_unique<UpdateManifestFetcher>(
-      update_manifest_url_, kUpdateManifestFetchTrafficAnnotation,
+      task_params_.update_manifest_url, kUpdateManifestFetchTrafficAnnotation,
       url_loader_factory_);
   update_manifest_fetcher_->FetchUpdateManifest(base::BindOnce(
       &IsolatedWebAppUpdateDiscoveryTask::OnUpdateManifestFetched,
@@ -210,7 +210,8 @@
           .Set("src", latest_version_entry->src().spec()));
 
   ASSIGN_OR_RETURN(
-      const WebApp& iwa, GetIsolatedWebAppById(*registrar_, url_info_.app_id()),
+      const WebApp& iwa,
+      GetIsolatedWebAppById(*registrar_, task_params_.url_info.app_id()),
       [&](const std::string&) { FailWith(Error::kIwaNotInstalled); });
   const auto& isolation_data = *iwa.isolation_data();
   base::Version currently_installed_version = isolation_data.version();
@@ -221,12 +222,12 @@
 
   bool same_version_update_allowed_by_key_rotation = false;
   bool pending_info_overwrite_allowed_by_key_rotation = false;
-  switch (LookupRotatedKey(url_info_.web_bundle_id(), debug_log_)) {
+  switch (LookupRotatedKey(task_params_.url_info.web_bundle_id(), debug_log_)) {
     case KeyRotationLookupResult::kNoKeyRotation:
       break;
     case KeyRotationLookupResult::kKeyFound: {
-      KeyRotationData data =
-          GetKeyRotationData(url_info_.web_bundle_id(), isolation_data);
+      KeyRotationData data = GetKeyRotationData(
+          task_params_.url_info.web_bundle_id(), isolation_data);
       if (!data.current_installation_has_rk) {
         same_version_update_allowed_by_key_rotation = true;
       }
@@ -301,12 +302,22 @@
     return;
   }
 
+  // Both prepare-update and apply-update tasks expect that the location type
+  // (dev mode / prod mode) stays unchanged between updates. For this reason,
+  // the update info is wrapped accordingly depending on `dev_mode`.
+  auto update_info =
+      task_params_.dev_mode
+          ? IsolatedWebAppUpdatePrepareAndStoreCommand::UpdateInfo(
+                IwaSourceBundleDevModeWithFileOp(
+                    bundle_.path(), IwaSourceBundleDevFileOp::kMove),
+                expected_version)
+          : IsolatedWebAppUpdatePrepareAndStoreCommand::UpdateInfo(
+                IwaSourceBundleProdModeWithFileOp(
+                    bundle_.path(), IwaSourceBundleProdFileOp::kMove),
+                expected_version);
+
   command_scheduler_->PrepareAndStoreIsolatedWebAppUpdate(
-      IsolatedWebAppUpdatePrepareAndStoreCommand::UpdateInfo(
-          IwaSourceBundleProdModeWithFileOp(bundle_.path(),
-                                            IwaSourceBundleProdFileOp::kMove),
-          expected_version),
-      url_info_,
+      update_info, task_params_.url_info,
       /*optional_keep_alive=*/nullptr,
       /*optional_profile_keep_alive=*/nullptr,
       base::BindOnce(&IsolatedWebAppUpdateDiscoveryTask::OnUpdateDryRunDone,
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task.h b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task.h
index ed593e2..c5d8b95 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task.h
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task.h
@@ -25,6 +25,12 @@
 
 namespace web_app {
 
+struct IwaUpdateDiscoveryTaskParams {
+  GURL update_manifest_url;
+  IsolatedWebAppUrlInfo url_info;
+  bool dev_mode;
+};
+
 class IsolatedWebAppUpdateDiscoveryTask {
  public:
   enum class Success {
@@ -57,8 +63,7 @@
   using CompletionCallback = base::OnceCallback<void(CompletionStatus status)>;
 
   IsolatedWebAppUpdateDiscoveryTask(
-      GURL update_manifest_url,
-      IsolatedWebAppUrlInfo url_info,
+      IwaUpdateDiscoveryTaskParams task_params,
       WebAppCommandScheduler& command_scheduler,
       WebAppRegistrar& registrar,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
@@ -72,7 +77,9 @@
   void Start(CompletionCallback callback);
   bool has_started() const { return has_started_; }
 
-  const IsolatedWebAppUrlInfo& url_info() const { return url_info_; }
+  const IsolatedWebAppUrlInfo& url_info() const {
+    return task_params_.url_info;
+  }
 
   base::Value AsDebugValue() const;
 
@@ -100,8 +107,7 @@
   bool has_started_ = false;
   CompletionCallback callback_;
 
-  GURL update_manifest_url_;
-  IsolatedWebAppUrlInfo url_info_;
+  const IwaUpdateDiscoveryTaskParams task_params_;
 
   raw_ref<WebAppCommandScheduler> command_scheduler_;
   raw_ref<WebAppRegistrar> registrar_;
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task_unittest.cc b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task_unittest.cc
index 8a5d2390..7cdaef27 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task_unittest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_discovery_task_unittest.cc
@@ -78,6 +78,14 @@
         fake_provider().web_contents_manager());
   }
 
+  Task CreateDefaultIwaUpdateDiscoveryTask() {
+    return Task({.update_manifest_url = update_manifest_url_,
+                 .url_info = url_info_,
+                 .dev_mode = false},
+                fake_provider().scheduler(), fake_provider().registrar_unsafe(),
+                profile()->GetURLLoaderFactory());
+  }
+
   base::test::ScopedFeatureList scoped_feature_list_;
   data_decoder::test::InProcessDataDecoder data_decoder_;
 
@@ -97,9 +105,7 @@
   profile_url_loader_factory().AddResponse(update_manifest_url_.spec(), "",
                                            net::HttpStatusCode::HTTP_NOT_FOUND);
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
@@ -111,9 +117,7 @@
   profile_url_loader_factory().AddResponse(update_manifest_url_.spec(),
                                            "invalid json");
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
@@ -123,9 +127,7 @@
 TEST_F(IsolatedWebAppUpdateDiscoveryTaskUpdateManifestTest, InvalidManifest) {
   profile_url_loader_factory().AddResponse(update_manifest_url_.spec(), "[]");
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
@@ -139,9 +141,7 @@
     { "versions": [] }
   )");
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
@@ -158,9 +158,7 @@
     }
   )");
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
@@ -178,9 +176,7 @@
     }
   )");
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
@@ -203,9 +199,7 @@
     }
   )");
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
@@ -233,9 +227,7 @@
     }
   )");
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
@@ -266,9 +258,7 @@
                                            "",
                                            net::HttpStatusCode::HTTP_NOT_FOUND);
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
@@ -359,9 +349,7 @@
   auto& page_state = CreateUpdateManifesteAndBundle(base::Version("3.0.0"));
   page_state.error_code = webapps::InstallableStatusCode::CANNOT_DOWNLOAD_ICON;
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
@@ -386,9 +374,7 @@
   InstallIwa(base::Version("1.0.0"));
   CreateUpdateManifesteAndBundle(base::Version("3.0.0"));
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
@@ -426,9 +412,7 @@
           base::Version("3.0.0"), /*integrity_block_data=*/std::nullopt));
   CreateUpdateManifesteAndBundle(base::Version("2.0.0"));
 
-  Task task(update_manifest_url_, url_info_, fake_provider().scheduler(),
-            fake_provider().registrar_unsafe(),
-            profile()->GetURLLoaderFactory());
+  Task task = CreateDefaultIwaUpdateDiscoveryTask();
 
   base::test::TestFuture<Task::CompletionStatus> future;
   task.Start(future.GetCallback());
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager.cc b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager.cc
index 2d2cab703..563f75a 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager.cc
@@ -335,6 +335,20 @@
   return queued_update_discovery_task;
 }
 
+void IsolatedWebAppUpdateManager::DiscoverUpdatesForApp(
+    const IsolatedWebAppUrlInfo& url_info,
+    const GURL& update_manifest_url,
+    bool dev_mode) {
+  task_queue_.Push(std::make_unique<IsolatedWebAppUpdateDiscoveryTask>(
+      IwaUpdateDiscoveryTaskParams{.update_manifest_url = update_manifest_url,
+                                   .url_info = url_info,
+                                   .dev_mode = dev_mode},
+      provider_->scheduler(), provider_->registrar_unsafe(),
+      profile_->GetURLLoaderFactory()));
+
+  task_queue_.MaybeStartNextTask();
+}
+
 size_t IsolatedWebAppUpdateManager::DiscoverUpdatesNow() {
   // If an update discovery check is already scheduled, reset it, so that the
   // next update discovery happens based on `update_discovery_frequency_` time
@@ -479,9 +493,7 @@
     // uninstalled), so no need to check for updates.
     return false;
   }
-  task_queue_.Push(std::make_unique<IsolatedWebAppUpdateDiscoveryTask>(
-      *update_manifest_url, url_info, provider_->scheduler(),
-      provider_->registrar_unsafe(), profile_->GetURLLoaderFactory()));
+  DiscoverUpdatesForApp(url_info, *update_manifest_url, /*dev_mode=*/false);
 
   return true;
 }
@@ -529,6 +541,10 @@
     IsolatedWebAppUpdateDiscoveryTask::CompletionStatus status) {
   TrackResultOfUpdateDiscoveryTask(status);
 
+  for (auto& observer : task_observers_) {
+    observer.OnUpdateDiscoveryTaskCompleted(task->url_info().app_id(), status);
+  }
+
   if (status.has_value() && *status ==
                                 IsolatedWebAppUpdateDiscoveryTask::Success::
                                     kUpdateFoundAndSavedInDatabase) {
@@ -567,6 +583,10 @@
     IsolatedWebAppUpdateApplyTask::CompletionStatus status) {
   TrackResultOfUpdateApplyTask(status);
 
+  for (auto& observer : task_observers_) {
+    observer.OnUpdateApplyTaskCompleted(task->url_info().app_id(), status);
+  }
+
   auto callbacks_it =
       on_update_finished_callbacks_.find(task->url_info().app_id());
   if (callbacks_it != on_update_finished_callbacks_.end()) {
@@ -591,6 +611,14 @@
   }
 }
 
+void IsolatedWebAppUpdateManager::AddObserver(Observer* observer) {
+  task_observers_.AddObserver(observer);
+}
+
+void IsolatedWebAppUpdateManager::RemoveObserver(Observer* observer) {
+  task_observers_.RemoveObserver(observer);
+}
+
 void IsolatedWebAppUpdateManager::OnLocalUpdateDiscovered(
     IsolatedWebAppUrlInfo url_info,
     base::OnceCallback<void(base::expected<base::Version, std::string>)>
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager.h b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager.h
index 68dabe3..1eb5e9b2 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager.h
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager.h
@@ -19,6 +19,8 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ref.h"
 #include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
 #include "base/scoped_observation.h"
 #include "base/time/time.h"
 #include "base/types/pass_key.h"
@@ -83,6 +85,19 @@
     : public WebAppInstallManagerObserver,
       public IwaKeyDistributionInfoProvider::Observer {
  public:
+  class Observer : public base::CheckedObserver {
+   public:
+    virtual void OnUpdateDiscoveryTaskCompleted(
+        const webapps::AppId& app_id,
+        IsolatedWebAppUpdateDiscoveryTask::CompletionStatus status) {}
+
+    // Will be invoked only if the discovery task finished with
+    // `kUpdateFoundAndSavedInDatabase`.
+    virtual void OnUpdateApplyTaskCompleted(
+        const webapps::AppId& app_id,
+        IsolatedWebAppUpdateApplyTask::CompletionStatus status) {}
+  };
+
   explicit IsolatedWebAppUpdateManager(
       Profile& profile,
       base::TimeDelta update_discovery_frequency =
@@ -137,11 +152,19 @@
       const webapps::AppId& app_id,
       webapps::WebappUninstallSource uninstall_source) override;
 
-  // Queues an update discovery task for the provided `app_id`. Returns a
-  // boolean indicating whether an update discovery task was queued
-  // successfully.
+  // Queues an update discovery task for the provided `app_id`, assuming that
+  // the corresponding app is policy-installed (prod mode). Returns a boolean
+  // indicating whether an update discovery task was queued successfully.
   bool MaybeDiscoverUpdatesForApp(const webapps::AppId& app_id);
 
+  // Queues an update discovery task (and potentially an apply update task
+  // afterwards if the discovery leads to a pending update) for the provided
+  // `url_info.app_id`. The result of the discover & apply chain will be
+  // communicated via observers.
+  void DiscoverUpdatesForApp(const IsolatedWebAppUrlInfo& url_info,
+                             const GURL& update_manifest_url,
+                             bool dev_mode);
+
   // Used to queue update discovery tasks manually from the
   // chrome://web-app-internals page. Returns the number of tasks queued.
   size_t DiscoverUpdatesNow();
@@ -169,6 +192,9 @@
     TrackResultOfUpdateApplyTask(status);
   }
 
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
  private:
   // This queue manages update discovery and apply tasks. Tasks can be added to
   // the queue via its `Push` methods. The queue will never start a new task on
@@ -360,6 +386,8 @@
   std::unique_ptr<LocalDevModeUpdateDiscoverer>
       local_dev_mode_update_discoverer_;
 
+  base::ObserverList<Observer> task_observers_;
+
   base::WeakPtrFactory<IsolatedWebAppUpdateManager> weak_factory_{this};
 
   IsolatedWebAppUpdateError FromDiscoveryTaskError(
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolation_data.cc b/chrome/browser/web_applications/isolated_web_apps/isolation_data.cc
index b064060..b6eb2a2 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolation_data.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolation_data.cc
@@ -10,6 +10,19 @@
 
 namespace web_app {
 
+namespace {
+
+void PersistFieldsForUpdateImpl(IsolationData::Builder& builder,
+                                const IsolationData& isolation_data) {
+  builder.SetControlledFramePartitions(
+      isolation_data.controlled_frame_partitions());
+  if (isolation_data.update_manifest_url()) {
+    builder.SetUpdateManifestUrl(*isolation_data.update_manifest_url());
+  }
+}
+
+}  // namespace
+
 IsolationData::IsolationData(
     IsolatedWebAppStorageLocation location,
     base::Version version,
@@ -164,6 +177,18 @@
   return std::move(*this);
 }
 
+IsolationData::Builder& IsolationData::Builder::PersistFieldsForUpdate(
+    const IsolationData& isolation_data) & {
+  PersistFieldsForUpdateImpl(*this, isolation_data);
+  return *this;
+}
+
+IsolationData::Builder&& IsolationData::Builder::PersistFieldsForUpdate(
+    const IsolationData& isolation_data) && {
+  PersistFieldsForUpdateImpl(*this, isolation_data);
+  return std::move(*this);
+}
+
 IsolationData IsolationData::Builder::Build() && {
   return IsolationData(
       std::move(location_), std::move(version_),
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolation_data.h b/chrome/browser/web_applications/isolated_web_apps/isolation_data.h
index 86c09fe..2511dd6f 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolation_data.h
+++ b/chrome/browser/web_applications/isolated_web_apps/isolation_data.h
@@ -142,8 +142,17 @@
     Builder& SetUpdateManifestUrl(GURL update_manifest_url) &;
     Builder&& SetUpdateManifestUrl(GURL update_manifest_url) &&;
 
+    // During an update the foundational pieces of the IWA (`location` and
+    // `version`) of the IWA change, and hence the IsolationData has to be
+    // re-built from scratch. This function is called as part of the update
+    // finalize routine -- all fields that have to be persisted (such as
+    // `controlled_frame_partitions`, etc) can be copied over here.
+    Builder& PersistFieldsForUpdate(const IsolationData& isolation_data) &;
+    Builder&& PersistFieldsForUpdate(const IsolationData& isolation_data) &&;
+
     // When adding new setters to the builder, make sure to update the the
-    // Builder(const IsolationData&) constructor to forward the new field.
+    // Builder(const IsolationData&) constructor to forward the new field as
+    // well as PersistFieldsForUpdate(const IsolationData&) if necessary.
     IsolationData Build() &&;
 
    private:
diff --git a/chrome/browser/web_applications/os_integration/mac/web_app_shortcut_creator.mm b/chrome/browser/web_applications/os_integration/mac/web_app_shortcut_creator.mm
index 6464f38..7bf516d9 100644
--- a/chrome/browser/web_applications/os_integration/mac/web_app_shortcut_creator.mm
+++ b/chrome/browser/web_applications/os_integration/mac/web_app_shortcut_creator.mm
@@ -1053,9 +1053,8 @@
 
   CFDataRef cd_hash_data = base::apple::GetValueFromDictionary<CFDataRef>(
       app_shim_info.get(), kSecCodeInfoUnique);
-  std::vector<uint8_t> cd_hash(
-      CFDataGetBytePtr(cd_hash_data),
-      CFDataGetBytePtr(cd_hash_data) + CFDataGetLength(cd_hash_data));
+  auto cd_hash_span = base::apple::CFDataToSpan(cd_hash_data);
+  std::vector<uint8_t> cd_hash(cd_hash_span.begin(), cd_hash_span.end());
 
   content::GetUIThreadTaskRunner()->PostTask(
       FROM_HERE, base::BindOnce(&AppShimRegistry::SaveCdHashForApp,
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index 65fae8be..34d569ee 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -507,12 +507,10 @@
   CHECK(version.IsValid());
 
   IsolationData::Builder builder(location, version);
-  if (web_app->isolation_data().has_value()) {
-    // If previous `controlled_frame_partitions` exist, keep them the same. This
-    // can only happen during an update, and never during an install.
-    builder.SetControlledFramePartitions(
-        web_app->isolation_data()->controlled_frame_partitions());
+  if (web_app->isolation_data()) {
+    builder.PersistFieldsForUpdate(*web_app->isolation_data());
   }
+
   if (integrity_block_data) {
     builder.SetIntegrityBlockData(std::move(*integrity_block_data));
   }
diff --git a/chrome/browser/web_applications/web_app_internals_browsertest.cc b/chrome/browser/web_applications/web_app_internals_browsertest.cc
index 6814f5d9..6845f02 100644
--- a/chrome/browser/web_applications/web_app_internals_browsertest.cc
+++ b/chrome/browser/web_applications/web_app_internals_browsertest.cc
@@ -51,6 +51,7 @@
 using ::testing::ElementsAre;
 using ::testing::Eq;
 using ::testing::Field;
+using ::testing::HasSubstr;
 using ::testing::Pointee;
 
 constexpr char kBadIconErrorTemplate[] = R"({
@@ -234,7 +235,7 @@
 };
 
 IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest,
-                       FetchUpdateManifestAndInstallIwa) {
+                       FetchUpdateManifestAndInstallIwaAndUpdate) {
   update_server_mixin_.AddBundle(
       IsolatedWebAppBuilder(ManifestBuilder().SetVersion("1.0.0"))
           .BuildBundle(test::GetDefaultEd25519KeyPair()));
@@ -269,14 +270,44 @@
                                               install_future.GetCallback());
   ASSERT_TRUE(install_future.Take()->is_success());
 
-  ASSERT_OK_AND_ASSIGN(
-      const WebApp& iwa,
-      GetIsolatedWebAppById(provider().registrar_unsafe(),
-                            IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId(
-                                test::GetDefaultEd25519WebBundleId())
-                                .app_id()));
+  webapps::AppId app_id = IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId(
+                              test::GetDefaultEd25519WebBundleId())
+                              .app_id();
+  {
+    ASSERT_OK_AND_ASSIGN(
+        const WebApp& iwa,
+        GetIsolatedWebAppById(provider().registrar_unsafe(), app_id));
 
-  EXPECT_EQ(iwa.isolation_data()->update_manifest_url(), update_manifest_url);
+    EXPECT_EQ(iwa.isolation_data()->version(), base::Version("1.0.0"));
+    EXPECT_EQ(iwa.isolation_data()->update_manifest_url(), update_manifest_url);
+  }
+
+  // Run an update check on the same manifest.
+  {
+    base::test::TestFuture<std::string> update_future;
+    handler->UpdateManifestInstalledIsolatedWebApp(
+        app_id, update_future.GetCallback<const std::string&>());
+    EXPECT_THAT(update_future.Get(),
+                HasSubstr("app is already on the latest version"));
+  }
+
+  // Now add a new entry to the manifest and re-run the update check.
+  update_server_mixin_.AddBundle(
+      IsolatedWebAppBuilder(ManifestBuilder().SetVersion("2.0.0"))
+          .BuildBundle(test::GetDefaultEd25519KeyPair()));
+  {
+    base::test::TestFuture<std::string> update_future;
+    handler->UpdateManifestInstalledIsolatedWebApp(
+        app_id, update_future.GetCallback<const std::string&>());
+    EXPECT_THAT(update_future.Get(), HasSubstr("Update to v2.0.0 successful"));
+
+    ASSERT_OK_AND_ASSIGN(
+        const WebApp& iwa,
+        GetIsolatedWebAppById(provider().registrar_unsafe(), app_id));
+
+    EXPECT_EQ(iwa.isolation_data()->version(), base::Version("2.0.0"));
+    EXPECT_EQ(iwa.isolation_data()->update_manifest_url(), update_manifest_url);
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest,
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index a2bc247..89dd91e 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1727805595-7e9a010c1d8e5cb05c53975b96ca20fa6c385199-bdb53115de59bca1fe7e29f68cb950f5b1280da2.profdata
+chrome-mac-arm-main-1727819901-b37f6aa3bd7e44031f4729dae847e40778147402-86bc96eaad068b892adea1e2f3a00a5139e608ea.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index e6f8560..1734086b 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1727783961-3b5411637555d3044fc3de4cf1811d2c1f21b15d-f7bc99221b34f0a43ca97da3ec7dcc2ae2e90c0c.profdata
+chrome-mac-main-1727805595-852726035f9707e3d9b8b076e732f598f852581e-bdb53115de59bca1fe7e29f68cb950f5b1280da2.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 10163fc..0dd674b 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1727773147-197eb862403676b83bf55c5aa212fa2559ac518a-a163f2d7227da4a5b9792888bd14304ff72ce6f2.profdata
+chrome-win32-main-1727783961-d6e6e6a32eaddcaec150fab8c6742dc2ca8117e2-f7bc99221b34f0a43ca97da3ec7dcc2ae2e90c0c.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 5a4b2b4..cfef90a8 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1727783961-fa1fbfa59af5e894dd8c60c24877d4aaf771b088-f7bc99221b34f0a43ca97da3ec7dcc2ae2e90c0c.profdata
+chrome-win64-main-1727794609-ab7d96d61ff551c0a6070c32e4483e09771f9eac-5301790cd7ef97088d4862465822da4cb2d95591.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 74834ccd..d461041 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -1640,15 +1640,6 @@
              "WebAppManifestPolicyAppIdentityUpdate",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-#if BUILDFLAG(IS_CHROMEOS)
-// Enables Isolated Web App context APIs in web kiosk sessions.
-// When enabled, Web App (PWA) kiosk session passes an isolated context check,
-// which makes blink expose IWA APIs to be used by the web app.
-BASE_FEATURE(kWebKioskEnableIwaApis,
-             "WebKioskEnableIwaApis",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-#endif
-
 #if !BUILDFLAG(IS_ANDROID)
 // Allow capturing of WebRTC event logs, and uploading of those logs to Crash.
 // Please note that a Chrome policy must also be set, for this to have effect.
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 83365ad..fca50b6 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -1010,10 +1010,6 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 BASE_DECLARE_FEATURE(kWebAppManifestPolicyAppIdentityUpdate);
 
-#if BUILDFLAG(IS_CHROMEOS)
-COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kWebKioskEnableIwaApis);
-#endif
-
 #if !BUILDFLAG(IS_ANDROID)
 COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kWebRtcRemoteEventLog);
 COMPONENT_EXPORT(CHROME_FEATURES)
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 9ca2211..10fc9a6d 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -291,7 +291,7 @@
 const char kExplicitlyAllowedPorts[] = "explicitly-allowed-ports";
 
 // Name of the command line flag to allow the ai data collection extension API.
-const char kExtensionAiDataCollection[] = "extension-ai-data-collection";
+const char kExtensionAiDataCollection[] = "enable-extension-ai-data-collection";
 
 // Name of the command line flag to force content verification to be on in one
 // of various modes.
diff --git a/chrome/common/controlled_frame/controlled_frame.cc b/chrome/common/controlled_frame/controlled_frame.cc
index 5dca166..f3c0c67 100644
--- a/chrome/common/controlled_frame/controlled_frame.cc
+++ b/chrome/common/controlled_frame/controlled_frame.cc
@@ -10,6 +10,7 @@
 #include "base/containers/span.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/initialize_extensions_client.h"
+#include "chrome/common/url_constants.h"
 #include "components/version_info/version_info.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/features/feature.h"
@@ -17,21 +18,6 @@
 #include "extensions/common/mojom/context_type.mojom.h"
 #include "third_party/blink/public/common/features.h"
 
-#if BUILDFLAG(IS_CHROMEOS)
-#include "base/command_line.h"
-#include "chrome/common/chrome_switches.h"
-#include "url/url_constants.h"
-#endif
-
-#if BUILDFLAG(IS_CHROMEOS)
-namespace {
-bool IsRunningInKioskMode() {
-  return base::CommandLine::ForCurrentProcess()->HasSwitch(
-      switches::kForceAppMode);
-}
-}  // namespace
-#endif
-
 base::span<const char* const> GetControlledFrameFeatureList() {
   static constexpr const char* feature_list[] = {
       "controlledFrameInternal", "chromeWebViewInternal", "guestViewInternal",
@@ -78,21 +64,9 @@
     return false;
   }
 
-  bool is_allowed_for_scheme = url.SchemeIs("isolated-app");
-
-#if BUILDFLAG(IS_CHROMEOS)
-  // Also allow API exposure in ChromeOS Kiosk mode for web apps.
-  if (base::FeatureList::IsEnabled(features::kWebKioskEnableIwaApis) &&
-      IsRunningInKioskMode() && url.SchemeIs(url::kHttpsScheme)) {
-    is_allowed_for_scheme =
-        extensions::GetCurrentChannel() != version_info::Channel::BETA &&
-        extensions::GetCurrentChannel() != version_info::Channel::STABLE;
-  }
-#endif
-
   // Verify that the current context is an Isolated Web App and the API name is
   // in our expected list.
-  return !extension && is_allowed_for_scheme &&
+  return (extension == nullptr) && url.SchemeIs(chrome::kIsolatedAppScheme) &&
          context == extensions::mojom::ContextType::kWebPage &&
          context_data.HasControlledFrameCapability() &&
          base::Contains(GetControlledFrameFeatureList(), api_full_name);
diff --git a/chrome/common/extensions/api/permissions.json b/chrome/common/extensions/api/permissions.json
index 04a0ff5..47ef94b 100644
--- a/chrome/common/extensions/api/permissions.json
+++ b/chrome/common/extensions/api/permissions.json
@@ -189,6 +189,11 @@
                 "type": "number",
                 "optional": true,
                 "description": "The id of the tab where site access request will be removed. This or `documentId` must be specified."
+              },
+              "pattern": {
+                "type": "string",
+                "optional": true,
+                "description": "The URL pattern where site access request will be removed. If provided, this must exactly match the pattern of an existing site access request."
               }
             }
           }
diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc
index d3980eae..0e6b4ef 100644
--- a/chrome/common/extensions/permissions/chrome_api_permissions.cc
+++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc
@@ -43,6 +43,7 @@
     {APIPermissionID::kDownloadsUi, "downloads.ui"},
     {APIPermissionID::kExperimental, "experimental",
      APIPermissionInfo::kFlagCannotBeOptional},
+    {APIPermissionID::kExperimentalAiData, "experimentalAiData"},
     {APIPermissionID::kGcm, "gcm",
      APIPermissionInfo::kFlagDoesNotRequireManagedSessionFullLoginWarning},
     {APIPermissionID::kGeolocation, "geolocation",
diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc
index 41f95c4..8938de6 100644
--- a/chrome/common/extensions/permissions/permission_set_unittest.cc
+++ b/chrome/common/extensions/permissions/permission_set_unittest.cc
@@ -823,6 +823,11 @@
   // to warn you further.
   skip.insert(APIPermissionID::kExperimental);
 
+  // The Experimental AI Data API is gated on commandline switches, in
+  // addition to the permission in the manifest. If you've turned on the
+  // experimental AI Data command-line flag, we don't need to warn you further.
+  skip.insert(APIPermissionID::kExperimentalAiData);
+
   // The Identity API has its own server-driven permission prompts.
   skip.insert(APIPermissionID::kIdentity);
 
diff --git a/chrome/test/data/click.html b/chrome/test/data/click.html
index ab726b0..842c064 100644
--- a/chrome/test/data/click.html
+++ b/chrome/test/data/click.html
@@ -2,6 +2,7 @@
 <html>
 <body>
 <button id="button">Button</button>
+<p><a id="link" href="links.html">Link</a></p>
 <script>
 const button = document.getElementById('button');
 button.addEventListener('click', function(event) {
diff --git a/chrome/test/data/extensions/.eslintrc.js b/chrome/test/data/extensions/.eslintrc.js
deleted file mode 100644
index 52bc790..0000000
--- a/chrome/test/data/extensions/.eslintrc.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// We have a relaxed version of the eslintrc file for
-// //chrome/test/data/extensions because it's massively out-of-sync with our
-// JS style guide. See https://crbug.com/941239.
-module.exports = {
-  'root': true,
-  'env': {
-    'browser': true,
-    'es6': true,
-  },
-  'parserOptions': {
-    'ecmaVersion': 2017,
-  },
-  'rules': {
-    // Enabled checks.
-    'no-extra-semi': 'error',
-    'semi': ['error', 'always'],
-    // Disabled checks.
-    'no-var': 'off',
-    'prefer-const': 'off',
-  },
-};
diff --git a/chrome/test/data/extensions/api_test/permissions/remove_site_access_request/worker.js b/chrome/test/data/extensions/api_test/permissions/remove_site_access_request/worker.js
index 37a6ff81..f15eba8 100644
--- a/chrome/test/data/extensions/api_test/permissions/remove_site_access_request/worker.js
+++ b/chrome/test/data/extensions/api_test/permissions/remove_site_access_request/worker.js
@@ -68,5 +68,19 @@
             `exist.`);
 
     chrome.test.succeed();
+  },
+
+  // Tests that an error is returned when the request to remove has an invalid
+  // pattern.
+  async function invalidPattern() {
+    let tab = await navigateTo('requested.com');
+
+    const request = {tabId: tab.id, pattern: 'invalid pattern'};
+    await chrome.test.assertPromiseRejects(
+        chrome.permissions.removeSiteAccessRequest(request),
+        `Error: Extension cannot remove a request with an invalid value for ` +
+            `'pattern'.`);
+
+    chrome.test.succeed();
   }
 ])
diff --git a/chrome/test/data/pdf/.eslintrc.js b/chrome/test/data/pdf/.eslintrc.js
deleted file mode 100644
index f396bca..0000000
--- a/chrome/test/data/pdf/.eslintrc.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  'env': {'browser': true, 'es6': true},
-  'rules': {
-    'no-restricted-properties': 'off',
-    'eqeqeq': ['error', 'always', {'null': 'ignore'}],
-  },
-
-  'overrides': [{
-    'files': ['**/*.ts'],
-    'parser': '../../../../third_party/node/node_modules/@typescript-eslint/parser/dist/index.js',
-    'plugins': [
-      '@typescript-eslint',
-    ],
-    'rules': {
-      '@typescript-eslint/consistent-type-imports': 'error',
-      '@typescript-eslint/naming-convention': [
-        'error',
-        // Override default format to allow test functions like testFoo_bar().
-        {
-          selector: 'function',
-          format: null,
-        },
-      ],
-    },
-  }],
-};
diff --git a/chrome/test/data/pdf/fullscreen_test.ts b/chrome/test/data/pdf/fullscreen_test.ts
index 3b6cf54..1eaef36 100644
--- a/chrome/test/data/pdf/fullscreen_test.ts
+++ b/chrome/test/data/pdf/fullscreen_test.ts
@@ -168,15 +168,15 @@
 
     chrome.test.succeed();
   },
-  async function testEnterAndExitFullscreenWithType_FitToPage() {
+  async function testEnterAndExitFullscreenWithTypeFitToPage() {
     await assertEnterAndExitFullscreenWithType(FittingType.FIT_TO_PAGE);
     chrome.test.succeed();
   },
-  async function testEnterAndExitFullscreenWithType_FitToWidth() {
+  async function testEnterAndExitFullscreenWithTypeFitToWidth() {
     await assertEnterAndExitFullscreenWithType(FittingType.FIT_TO_WIDTH);
     chrome.test.succeed();
   },
-  async function testEnterAndExitFullscreenWithType_FitToHeight() {
+  async function testEnterAndExitFullscreenWithTypeFitToHeight() {
     await assertEnterAndExitFullscreenWithType(FittingType.FIT_TO_HEIGHT);
     chrome.test.succeed();
   },
diff --git a/chrome/test/data/pdf/metrics_test.ts b/chrome/test/data/pdf/metrics_test.ts
index c0c7fc2..94bac25dc 100644
--- a/chrome/test/data/pdf/metrics_test.ts
+++ b/chrome/test/data/pdf/metrics_test.ts
@@ -7,15 +7,8 @@
 chrome.test.runTests(function() {
   'use strict';
 
-  const originalMetricTypeType = chrome.metricsPrivate.MetricTypeType;
-
   class MockMetricsPrivate {
     actionCounter: Map<UserAction, number> = new Map();
-    MetricTypeType: typeof chrome.metricsPrivate.MetricTypeType;
-
-    constructor() {
-      this.MetricTypeType = originalMetricTypeType;
-    }
 
     recordValue(metric: chrome.metricsPrivate.MetricType, value: number) {
       chrome.test.assertEq('PDF.Actions', metric.metricName);
@@ -34,8 +27,8 @@
     function testMetricsDocumentOpened() {
       resetMetricsForTesting();
       const mockMetricsPrivate = new MockMetricsPrivate();
-      chrome.metricsPrivate =
-          mockMetricsPrivate as unknown as typeof chrome.metricsPrivate;
+      chrome.metricsPrivate.recordValue =
+          mockMetricsPrivate.recordValue.bind(mockMetricsPrivate);
 
       record(UserAction.DOCUMENT_OPENED);
 
@@ -50,8 +43,8 @@
     function testMetricsFirstRecorded() {
       resetMetricsForTesting();
       const mockMetricsPrivate = new MockMetricsPrivate();
-      chrome.metricsPrivate =
-          mockMetricsPrivate as unknown as typeof chrome.metricsPrivate;
+      chrome.metricsPrivate.recordValue =
+          mockMetricsPrivate.recordValue.bind(mockMetricsPrivate);
 
       const keys = (Object.keys(UserAction) as Array<keyof typeof UserAction>)
                        .filter(key => Number.isInteger(UserAction[key]))
@@ -82,8 +75,8 @@
     function testMetricsFitTo() {
       resetMetricsForTesting();
       const mockMetricsPrivate = new MockMetricsPrivate();
-      chrome.metricsPrivate =
-          mockMetricsPrivate as unknown as typeof chrome.metricsPrivate;
+      chrome.metricsPrivate.recordValue =
+          mockMetricsPrivate.recordValue.bind(mockMetricsPrivate);
 
       record(UserAction.DOCUMENT_OPENED);
       recordFitTo(FittingType.FIT_TO_HEIGHT);
diff --git a/chrome/test/data/pdf/test_util.ts b/chrome/test/data/pdf/test_util.ts
index f7c4909..ec02212 100644
--- a/chrome/test/data/pdf/test_util.ts
+++ b/chrome/test/data/pdf/test_util.ts
@@ -34,7 +34,7 @@
     this.sizer = sizer;
 
     if (sizer) {
-      sizer.resizeCallback_ = () =>
+      sizer.resizeCallbackImpl = () =>
           this.scrollTo(this.scrollLeft, this.scrollTop);
     }
   }
@@ -75,7 +75,7 @@
   private width_: string = '0px';
   private height_: string = '0px';
 
-  resizeCallback_: (() => void)|null = null;
+  resizeCallbackImpl: (() => void)|null = null;
   style: {
     height: string,
     width: string,
@@ -92,8 +92,8 @@
 
       set height(height: string) {
         sizer.height_ = height;
-        if (sizer.resizeCallback_) {
-          sizer.resizeCallback_();
+        if (sizer.resizeCallbackImpl) {
+          sizer.resizeCallbackImpl();
         }
       },
 
@@ -103,8 +103,8 @@
 
       set width(width: string) {
         sizer.width_ = width;
-        if (sizer.resizeCallback_) {
-          sizer.resizeCallback_();
+        if (sizer.resizeCallbackImpl) {
+          sizer.resizeCallbackImpl();
         }
       },
     };
diff --git a/chrome/test/data/pdf/viewport_test.ts b/chrome/test/data/pdf/viewport_test.ts
index b938185..b78c030b 100644
--- a/chrome/test/data/pdf/viewport_test.ts
+++ b/chrome/test/data/pdf/viewport_test.ts
@@ -58,7 +58,7 @@
     chrome.test.succeed();
   },
 
-  function testOverlayScrollbarWidth_local() {
+  function testOverlayScrollbarWidthLocal() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 43, 1);
 
@@ -66,7 +66,7 @@
     chrome.test.succeed();
   },
 
-  function testOverlayScrollbarWidth_remote() {
+  function testOverlayScrollbarWidthRemote() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 43, 1);
     viewport.setRemoteContent(createMockPdfPluginForTest());
@@ -1750,7 +1750,7 @@
     chrome.test.succeed();
   },
 
-  function testSetContent_showLocalSizer() {
+  function testSetContentShowLocalSizer() {
     const mockSizer = new MockSizer();
     const viewport =
         getZoomableViewport(new MockElement(100, 100, null), mockSizer, 0, 1);
@@ -1763,7 +1763,7 @@
     chrome.test.succeed();
   },
 
-  function testSetContent_sizeToLocal() {
+  function testSetContentSizeToLocal() {
     const mockSizer = new MockSizer();
     const viewport =
         getZoomableViewport(new MockElement(100, 100, null), mockSizer, 0, 1);
@@ -1780,7 +1780,7 @@
     chrome.test.succeed();
   },
 
-  function testSetContent_scrollToLocal() {
+  function testSetContentScrollToLocal() {
     const mockWindow = new MockElement(100, 100, null);
     const viewport = getZoomableViewport(mockWindow, new MockSizer(), 0, 1);
     viewport.setRemoteContent(createMockPdfPluginForTest());
@@ -1798,7 +1798,7 @@
     chrome.test.succeed();
   },
 
-  function testSetRemoteContent_attachContent() {
+  function testSetRemoteContentAttachContent() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 0, 1);
 
@@ -1810,7 +1810,7 @@
     chrome.test.succeed();
   },
 
-  function testSetRemoteContent_hideLocalSizer() {
+  function testSetRemoteContentHideLocalSizer() {
     const mockSizer = new MockSizer();
     const viewport =
         getZoomableViewport(new MockElement(100, 100, null), mockSizer, 0, 1);
@@ -1821,7 +1821,7 @@
     chrome.test.succeed();
   },
 
-  function testSetRemoteContent_sizeToRemote() {
+  function testSetRemoteContentSizeToRemote() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 0, 1);
     viewport.setDocumentDimensions(new MockDocumentDimensions(20, 30));
@@ -1835,7 +1835,7 @@
     chrome.test.succeed();
   },
 
-  function testSetRemoteContent_scrollToRemote() {
+  function testSetRemoteContentScrollToRemote() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 0, 1);
     viewport.setDocumentDimensions(new MockDocumentDimensions(200, 200));
@@ -1851,7 +1851,7 @@
     chrome.test.succeed();
   },
 
-  function testSetDocumentDimensions_remote() {
+  function testSetDocumentDimensionsRemote() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 0, 1);
     const mockPlugin = createMockPdfPluginForTest();
@@ -1868,7 +1868,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote() {
+  function testSetPositionRemote() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 0, 1);
     const mockPlugin = createMockPdfPluginForTest();
@@ -1887,7 +1887,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote_modifiedByAck() {
+  function testSetPositionRemoteModifiedByAck() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 0, 1);
     const mockPlugin = createMockPdfPluginForTest();
@@ -1906,7 +1906,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote_modifiedByAck_ignoreOverlapping() {
+  function testSetPositionRemoteModifiedByAckIgnoreOverlapping() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 0, 1);
     const mockPlugin = createMockPdfPluginForTest();
@@ -1926,7 +1926,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote_modifiedByAck_multiple() {
+  function testSetPositionRemoteModifiedByAckMultiple() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 0, 1);
     const mockPlugin = createMockPdfPluginForTest();
@@ -1947,7 +1947,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote_NaN() {
+  function testSetPositionRemoteNaN() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 0, 1);
     viewport.setRemoteContent(createMockPdfPluginForTest());
@@ -1959,7 +1959,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote_underflow_leftAndTop() {
+  function testSetPositionRemoteUnderflowLeftAndTop() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), SCROLLBAR_WIDTH, 1);
     viewport.setRemoteContent(createMockPdfPluginForTest());
@@ -1973,7 +1973,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote_underflow_rightAndTop() {
+  function testSetPositionRemoteUnderflowRightAndTop() {
     const mockWindow = new MockElement(100, 100, null);
     mockWindow.dir = 'rtl';
     const viewport =
@@ -1989,7 +1989,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote_overflow_rightAndBottom() {
+  function testSetPositionRemoteOverflowRightAndBottom() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), SCROLLBAR_WIDTH, 1);
     viewport.setRemoteContent(createMockPdfPluginForTest());
@@ -2003,7 +2003,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote_overflow_leftAndBottom() {
+  function testSetPositionRemoteOverflowLeftAndBottom() {
     const mockWindow = new MockElement(100, 100, null);
     mockWindow.dir = 'rtl';
     const viewport =
@@ -2019,7 +2019,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote_overflowWithoutVerticalScrollbar_right() {
+  function testSetPositionRemoteOverflowWithoutVerticalScrollbarRight() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), SCROLLBAR_WIDTH, 1);
     viewport.setRemoteContent(createMockPdfPluginForTest());
@@ -2033,7 +2033,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote_overflowWithoutVerticalScrollbar_left() {
+  function testSetPositionRemoteOverflowWithoutVerticalScrollbarLeft() {
     const mockWindow = new MockElement(100, 100, null);
     mockWindow.dir = 'rtl';
     const viewport =
@@ -2049,7 +2049,7 @@
     chrome.test.succeed();
   },
 
-  function testSetPosition_remote_overflowWithoutHorizontalScrollbar_bottom() {
+  function testSetPositionRemoteOverflowWithoutHorizontalScrollbarBottom() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), SCROLLBAR_WIDTH, 1);
     viewport.setRemoteContent(createMockPdfPluginForTest());
@@ -2079,7 +2079,7 @@
     chrome.test.succeed();
   },
 
-  function testSyncScrollFromRemote_duplicateScroll() {
+  function testSyncScrollFromRemoteDuplicateScroll() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 0, 1);
     const mockPlugin = createMockPdfPluginForTest();
@@ -2096,7 +2096,7 @@
     chrome.test.succeed();
   },
 
-  function testSyncScrollFromRemote_scrollToRemoteUnacked() {
+  function testSyncScrollFromRemoteScrollToRemoteUnacked() {
     const viewport = getZoomableViewport(
         new MockElement(100, 100, null), new MockSizer(), 0, 1);
     const mockPlugin = createMockPdfPluginForTest();
diff --git a/chrome/test/data/webui/.eslintrc.js b/chrome/test/data/webui/.eslintrc.js
deleted file mode 100644
index ee5bcc9f..0000000
--- a/chrome/test/data/webui/.eslintrc.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  // clang-format off
-  'env': {'browser': true, 'es6': true},
-  'rules': {
-    'no-restricted-properties': [
-      'error', {
-        'object': 'MockInteractions',
-        'property': 'tap',
-        'message': 'Do not use on-tap handlers in prod code, and use the ' +
-            'native click() method in tests. See more context at ' +
-            'crbug.com/812035.',
-      },
-      {
-        'object': 'test',
-        'property': 'only',
-        'message': 'test.only() silently disables other tests in the same ' +
-            'suite(). Did you forget deleting it before uploading? Use ' +
-            'test.skip() instead to explicitly disable certain test() cases.',
-      },
-    ],
-    'eqeqeq': ['error', 'always', {'null': 'ignore'}],
-  },
-  // clang-format on
-};
diff --git a/chrome/test/data/webui/chromeos/.eslintrc.js b/chrome/test/data/webui/chromeos/.eslintrc.js
deleted file mode 100644
index 50aca50..0000000
--- a/chrome/test/data/webui/chromeos/.eslintrc.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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.
-
-module.exports = {
-  // clang-format off
-  'ignorePatterns' : ['chai_v4.js'],
-  'overrides': [{
-    'files': ['**/*.ts'],
-    'parser': '../../../../../third_party/node/node_modules/@typescript-eslint/parser/dist/index.js',
-    'plugins': [
-      '@typescript-eslint',
-    ],
-    'rules': {
-      // Turn off until all violations under this folder are fixed. This was
-      // done for other parts of the codebase in http://crbug.com/1521107
-      'no-restricted-syntax': 'off',
-
-      // Turn off until all violations under this folder are fixed. This was
-      // done for other parts of the codebase in http://crbug.com/1494527
-      '@typescript-eslint/consistent-type-imports' : 'off',
-    },
-  }],
-  // clang-format on
-};
diff --git a/chrome/test/data/webui/chromeos/BUILD.gn b/chrome/test/data/webui/chromeos/BUILD.gn
index 37073e2..98f7c90 100644
--- a/chrome/test/data/webui/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/chromeos/BUILD.gn
@@ -238,7 +238,7 @@
     "network/network_property_list_mojo_test.js",
     "network/network_proxy_exclusions_test.js",
     "network/network_proxy_input_test.js",
-    "network/network_proxy_test.js",
+    "network/network_proxy_test.ts",
     "network/network_select_test.js",
     "network/network_siminfo_test.ts",
     "network/sim_lock_dialogs_test.js",
diff --git a/chrome/test/data/webui/chromeos/network/network_proxy_test.js b/chrome/test/data/webui/chromeos/network/network_proxy_test.js
deleted file mode 100644
index 40349ff..0000000
--- a/chrome/test/data/webui/chromeos/network/network_proxy_test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'chrome://os-settings/strings.m.js';
-import 'chrome://resources/ash/common/network/network_proxy.js';
-
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-
-suite('NetworkProxyTest', function() {
-  /** @type {!NetworkProxy|undefined} */
-  let netProxy;
-
-  function flushAsync() {
-    flush();
-    // Use setTimeout to wait for the next macrotask.
-    return new Promise(resolve => setTimeout(resolve));
-  }
-
-  setup(function() {
-    netProxy = document.createElement('network-proxy');
-    document.body.appendChild(netProxy);
-    flush();
-  });
-
-  test('Proxy select option change fires proxy-change event', function(done) {
-    const proxyType = netProxy.$.proxyType;
-
-    // Verify that changing the proxy type select option fires the proxy-change
-    // event with the new proxy type.
-    netProxy.addEventListener('proxy-change', function(e) {
-      assertEquals('WPAD', e.detail.type);
-      done();
-    });
-
-    // Simulate changing the proxy select option.
-    proxyType.value = 'WPAD';
-    proxyType.dispatchEvent(new Event('change'));
-  });
-
-  test(
-      'Add exception button only enabled when value in input',
-      async function() {
-        const button = netProxy.$.proxyExclusionButton;
-        const input = netProxy.$.proxyExclusion;
-        assertTrue(!!button);
-        assertTrue(!!input);
-        assertTrue(button.disabled);
-
-        // Simulate typing a letter.
-        input.value = 'A';
-
-        await flushAsync();
-        assertFalse(button.disabled);
-
-        // Simulate deleting the letter.
-        input.value = '';
-
-        await flushAsync();
-        assertTrue(button.disabled);
-      });
-});
diff --git a/chrome/test/data/webui/chromeos/network/network_proxy_test.ts b/chrome/test/data/webui/chromeos/network/network_proxy_test.ts
new file mode 100644
index 0000000..6b44207
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/network/network_proxy_test.ts
@@ -0,0 +1,65 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://os-settings/strings.m.js';
+import 'chrome://resources/ash/common/network/network_proxy.js';
+
+import type {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
+import type {CrInputElement} from 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
+import type {NetworkProxyElement} from 'chrome://resources/ash/common/network/network_proxy.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
+
+suite('NetworkProxyTest', () => {
+  let netProxy: NetworkProxyElement|undefined;
+
+  setup(() => {
+    netProxy = document.createElement('network-proxy');
+    document.body.appendChild(netProxy);
+    flush();
+  });
+
+  test('Proxy select option change fires proxy-change event', (done) => {
+    assertTrue(!!netProxy);
+    const proxyType =
+        netProxy.shadowRoot!.querySelector<HTMLSelectElement>('#proxyType');
+
+    // Verify that changing the proxy type select option fires the proxy-change
+    // event with the new proxy type.
+    netProxy.addEventListener('proxy-change', function(e: Event) {
+      const customEvent = e as CustomEvent;
+      assertEquals('WPAD', customEvent.detail.type);
+      done();
+    });
+
+    // Simulate changing the proxy select option.
+    assertTrue(!!proxyType);
+    proxyType.value = 'WPAD';
+    proxyType.dispatchEvent(new Event('change'));
+  });
+
+  test('Add exception button only enabled when value in input', async () => {
+    assertTrue(!!netProxy);
+    const button = netProxy.shadowRoot!.querySelector<CrButtonElement>(
+        '#proxyExclusionButton');
+    const input =
+        netProxy.shadowRoot!.querySelector<CrInputElement>('#proxyExclusion');
+    assertTrue(!!button);
+    assertTrue(!!input);
+    assertTrue(button.disabled);
+
+    // Simulate typing a letter.
+    input.value = 'A';
+
+    await flushTasks();
+    assertFalse(button.disabled);
+
+    // Simulate deleting the letter.
+    input.value = '';
+
+    await flushTasks();
+    assertTrue(button.disabled);
+  });
+});
diff --git a/chrome/test/data/webui/chromeos/settings/.eslintrc.js b/chrome/test/data/webui/chromeos/settings/.eslintrc.js
deleted file mode 100644
index 3ca03e6..0000000
--- a/chrome/test/data/webui/chromeos/settings/.eslintrc.js
+++ /dev/null
@@ -1,20 +0,0 @@
-// 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.
-
-module.exports = {
-  // clang-format off
-  'overrides': [{
-    'files': ['**/*.ts'],
-    'parser': '../../../../../../third_party/node/node_modules/@typescript-eslint/parser/dist/index.js',
-    'plugins': [
-      '@typescript-eslint',
-    ],
-    'rules': {
-      // Turn off until all violations under this folder are fixed. This was
-      // done for other parts of the codebase in http://crbug.com/1494527
-      '@typescript-eslint/consistent-type-imports': 'off',
-    },
-  }],
-  // clang-format on
-};
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.ts b/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.ts
index 685c4cc..d82c9e5 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.ts
+++ b/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.ts
@@ -300,9 +300,9 @@
     assertEquals(RmadErrorCode.kRequestInvalid, result.stateResult.error);
   });
 
-  // Verify `chooseManuallyDisableWriteProtect()` can be called from the correct
+  // Verify `setManuallyDisableWriteProtect()` can be called from the correct
   // state.
-  test('ChooseManuallyDisableWriteProtectOk', async () => {
+  test('SetManuallyDisableWriteProtectOk', async () => {
     const states = [
       {
         state: State.kChooseWriteProtectDisableMethod,
@@ -320,14 +320,14 @@
     assert(service);
     service.setStates(states);
 
-    const result = await service.chooseManuallyDisableWriteProtect();
+    const result = await service.setManuallyDisableWriteProtect();
     assertEquals(State.kUpdateOs, result.stateResult.state);
     assertEquals(RmadErrorCode.kOk, result.stateResult.error);
   });
 
-  // Verify `chooseManuallyDisableWriteProtect()` can't be called from the wrong
+  // Verify `setManuallyDisableWriteProtect()` can't be called from the wrong
   // state.
-  test('ChooseManuallyDisableWriteProtectWrongStateFails', async () => {
+  test('SetManuallyDisableWriteProtectWrongStateFails', async () => {
     const states = [
       {
         state: State.kWelcomeScreen,
@@ -345,14 +345,14 @@
     assert(service);
     service.setStates(states);
 
-    const result = await service.chooseManuallyDisableWriteProtect();
+    const result = await service.setManuallyDisableWriteProtect();
     assertEquals(State.kWelcomeScreen, result.stateResult.state);
     assertEquals(RmadErrorCode.kRequestInvalid, result.stateResult.error);
   });
 
-  // Verify `chooseRsuDisableWriteProtect()` can be called from the correct
+  // Verify `setRsuDisableWriteProtect()` can be called from the correct
   // state.
-  test('ChooseRsuDisableWriteProtectOk', async () => {
+  test('SetRsuDisableWriteProtectOk', async () => {
     const states = [
       {
         state: State.kChooseWriteProtectDisableMethod,
@@ -370,14 +370,14 @@
     assert(service);
     service.setStates(states);
 
-    const result = await service.chooseRsuDisableWriteProtect();
+    const result = await service.setRsuDisableWriteProtect();
     assertEquals(State.kUpdateOs, result.stateResult.state);
     assertEquals(RmadErrorCode.kOk, result.stateResult.error);
   });
 
-  // Verify `chooseRsuDisableWriteProtect()` can't be called from the wrong
+  // Verify `setRsuDisableWriteProtect()` can't be called from the wrong
   // state.
-  test('ChooseRsuDisableWriteProtectWrongStateFails', async () => {
+  test('SetRsuDisableWriteProtectWrongStateFails', async () => {
     const states = [
       {
         state: State.kWelcomeScreen,
@@ -395,7 +395,7 @@
     assert(service);
     service.setStates(states);
 
-    const result = await service.chooseRsuDisableWriteProtect();
+    const result = await service.setRsuDisableWriteProtect();
     assertEquals(State.kWelcomeScreen, result.stateResult.state);
     assertEquals(RmadErrorCode.kRequestInvalid, result.stateResult.error);
   });
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/onboarding_choose_wp_disable_method_page_test.ts b/chrome/test/data/webui/chromeos/shimless_rma/onboarding_choose_wp_disable_method_page_test.ts
index 6c97142..84a6e94 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/onboarding_choose_wp_disable_method_page_test.ts
+++ b/chrome/test/data/webui/chromeos/shimless_rma/onboarding_choose_wp_disable_method_page_test.ts
@@ -81,7 +81,7 @@
 
     const expectedPromise = new PromiseResolver<{stateResult: StateResult}>();
     assert(service);
-    service.chooseManuallyDisableWriteProtect = () => expectedPromise.promise;
+    service.setManuallyDisableWriteProtect = () => expectedPromise.promise;
 
     assert(component);
     const manualDisableComponent =
@@ -98,7 +98,7 @@
 
     const expectedPromise = new PromiseResolver<{stateResult: StateResult}>();
     assert(service);
-    service.chooseRsuDisableWriteProtect = () => expectedPromise.promise;
+    service.setRsuDisableWriteProtect = () => expectedPromise.promise;
 
     assert(component);
     const rsuDisableComponent =
diff --git a/chrome/test/data/webui/cr_components/certificate_manager/certificate_manager_v2_test_support.ts b/chrome/test/data/webui/cr_components/certificate_manager/certificate_manager_v2_test_support.ts
index 3abaf7c2..72954fb 100644
--- a/chrome/test/data/webui/cr_components/certificate_manager/certificate_manager_v2_test_support.ts
+++ b/chrome/test/data/webui/cr_components/certificate_manager/certificate_manager_v2_test_support.ts
@@ -59,6 +59,7 @@
       'importAndBindCertificate',
       'deleteCertificate',
       'showNativeManageCertificates',
+      'setIncludeSystemTrustStore',
     ]);
   }
 
@@ -119,6 +120,12 @@
     this.methodCalled('showNativeManageCertificates');
   }
   // </if>
+
+  // <if expr="not is_chromeos">
+  setIncludeSystemTrustStore(include: boolean) {
+    this.methodCalled('setIncludeSystemTrustStore', include);
+  }
+  // </if>
 }
 
 export class TestCertificateManagerProxy {
diff --git a/chrome/test/data/webui/cr_components/certificate_manager/local_certs_section_v2_test.ts b/chrome/test/data/webui/cr_components/certificate_manager/local_certs_section_v2_test.ts
index dec891c..cd48042 100644
--- a/chrome/test/data/webui/cr_components/certificate_manager/local_certs_section_v2_test.ts
+++ b/chrome/test/data/webui/cr_components/certificate_manager/local_certs_section_v2_test.ts
@@ -322,4 +322,42 @@
     assertFalse(localCertsSection.$.importOsCerts.disabled);
   });
   // </if>
+
+  // <if expr="not is_chromeos">
+  test('import OS certs toggle disable', async () => {
+    const metadata: CertManagementMetadata = {
+      includeSystemTrustStore: true,
+      numUserAddedSystemCerts: 0,
+      isIncludeSystemTrustStoreManaged: false,
+      numPolicyCerts: 0,
+    };
+    testProxy.handler.setCertManagementMetadata(metadata);
+    initializeElement();
+
+    await testProxy.handler.whenCalled('getCertManagementMetadata');
+    await microtasksFinished();
+
+    localCertsSection.$.importOsCerts.click();
+    await testProxy.handler.whenCalled('setIncludeSystemTrustStore');
+    assertFalse(testProxy.handler.getArgs('setIncludeSystemTrustStore')[0]);
+  });
+
+  test('import OS certs toggle enable', async () => {
+    const metadata: CertManagementMetadata = {
+      includeSystemTrustStore: false,
+      numUserAddedSystemCerts: 0,
+      isIncludeSystemTrustStoreManaged: false,
+      numPolicyCerts: 0,
+    };
+    testProxy.handler.setCertManagementMetadata(metadata);
+    initializeElement();
+
+    await testProxy.handler.whenCalled('getCertManagementMetadata');
+    await microtasksFinished();
+
+    localCertsSection.$.importOsCerts.click();
+    await testProxy.handler.whenCalled('setIncludeSystemTrustStore');
+    assertTrue(testProxy.handler.getArgs('setIncludeSystemTrustStore')[0]);
+  });
+  // </if>
 });
diff --git a/chrome/test/data/webui/cr_components/chromeos/BUILD.gn b/chrome/test/data/webui/cr_components/chromeos/BUILD.gn
index 5070331..27e4fd8 100644
--- a/chrome/test/data/webui/cr_components/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/cr_components/chromeos/BUILD.gn
@@ -70,7 +70,7 @@
     "network/network_property_list_mojo_test.js",
     "network/network_proxy_exclusions_test.js",
     "network/network_proxy_input_test.js",
-    "network/network_proxy_test.js",
+    "network/network_proxy_test.ts",
     "network/network_select_test.js",
     "network/network_siminfo_test.ts",
     "network/sim_lock_dialogs_test.js",
diff --git a/chrome/test/data/webui/cr_components/history_embeddings/history_embeddings_test.ts b/chrome/test/data/webui/cr_components/history_embeddings/history_embeddings_test.ts
index fe5a6f0..0e06d426 100644
--- a/chrome/test/data/webui/cr_components/history_embeddings/history_embeddings_test.ts
+++ b/chrome/test/data/webui/cr_components/history_embeddings/history_embeddings_test.ts
@@ -484,7 +484,7 @@
           '.answer-source');
       assertTrue(!!answerSource);
       assertFalse(answerSource.hidden);
-      assertEquals('http://answer.com', answerSource.getAttribute('href'));
+      assertEquals('http://answer.com/', answerSource.href);
 
       const answerUrlAndDate =
           answerSource.querySelector<HTMLElement>('.result-url')!.innerText;
@@ -496,6 +496,50 @@
                           '.favicon')!.style.backgroundImage);
     });
 
+    test('GetsAnswerSourceUrl', async () => {
+      if (!enableAnswers) {
+        return;
+      }
+
+      function sendAnswerWithTextDirectives(directives: string[] = []) {
+        const resultWithAnswer = {
+          title: 'Website with answer',
+          url: {url: 'http://answer.com'},
+          urlForDisplay: 'Answer.com',
+          relativeTime: '2 months ago',
+          shortDateTime: 'Jan 2, 2022',
+          sourcePassage: 'Answer description',
+          lastUrlVisitTimestamp: 2000,
+          answerData: {answerTextDirectives: directives},
+        };
+
+        element.searchResultChangedForTesting({
+          query: 'some query',
+          answerStatus: AnswerStatus.kSuccess,
+          answer: 'some answer',
+          items: [...mockResults, resultWithAnswer],
+        });
+
+        return flushTasks();
+      }
+
+      const answerSource = element.shadowRoot!.querySelector<HTMLAnchorElement>(
+          '.answer-source');
+      assertTrue(!!answerSource);
+      await sendAnswerWithTextDirectives([]);
+      assertEquals('http://answer.com/', answerSource.href);
+
+      await sendAnswerWithTextDirectives(['start text']);
+      assertEquals(
+          'http://answer.com/#:~:text=start%20text', answerSource.href,
+          'should generate a link using the first directive');
+
+      await sendAnswerWithTextDirectives(['another start text', 'end text']);
+      assertEquals(
+          'http://answer.com/#:~:text=another%20start%20text',
+          answerSource.href, 'should ignore other directives');
+    });
+
     test('DisplaysFavicons', async () => {
       if (!enableAnswers) {
         // Favicons for without answers is embedded in a separate component.
diff --git a/chrome/test/data/webui/flags/app_test.ts b/chrome/test/data/webui/flags/app_test.ts
index 2903a910..3ab5ffa9 100644
--- a/chrome/test/data/webui/flags/app_test.ts
+++ b/chrome/test/data/webui/flags/app_test.ts
@@ -9,7 +9,7 @@
 import {FlagsBrowserProxyImpl} from 'chrome://flags/flags_browser_proxy.js';
 import {getDeepActiveElement} from 'chrome://resources/js/util.js';
 import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
+import {eventToPromise, isVisible, microtasksFinished} from 'chrome://webui-test/test_util.js';
 
 import {TestFlagsBrowserProxy} from './test_flags_browser_proxy.js';
 
@@ -166,9 +166,9 @@
     await searchEventPromise;
     assertTrue(isVisible(clearSearch));
 
-    // The clear search button is pressed then search text is cleared and button
-    // is hidden
+    // The clear search button is hidden after clicked.
     clearSearch.click();
+    await microtasksFinished();
     assertEquals('', searchTextArea.value);
     assertFalse(isVisible(clearSearch));
   });
@@ -217,21 +217,21 @@
     searchBoxInput('available');
     return promise.then(() => {
       assertFalse(isVisible(app.getRequiredElement('.no-match')));
-      const noMatchMsg: NodeListOf<HTMLElement> =
-          app.shadowRoot!.querySelectorAll('.tab-content .no-match');
+      const noMatchMsg = app.shadowRoot!.querySelectorAll<HTMLElement>(
+          '.tab-content .no-match');
       assertTrue(!!noMatchMsg[0]);
       assertEquals(
           2,
           app.shadowRoot!
               .querySelectorAll(
-                  `#tab-content-available flags-experiment:not(.hidden)`)
+                  `#tab-content-available flags-experiment:not([hidden])`)
               .length);
       assertTrue(!!noMatchMsg[1]);
       assertEquals(
           1,
           app.shadowRoot!
               .querySelectorAll(
-                  `#tab-content-unavailable flags-experiment:not(.hidden)`)
+                  `#tab-content-unavailable flags-experiment:not([hidden])`)
               .length);
     });
   });
@@ -241,21 +241,21 @@
     searchBoxInput('none');
     return promise.then(() => {
       assertTrue(isVisible(app.getRequiredElement('.no-match')));
-      const noMatchMsg: NodeListOf<HTMLElement> =
-          app.shadowRoot!.querySelectorAll('.tab-content .no-match');
+      const noMatchMsg = app.shadowRoot!.querySelectorAll<HTMLElement>(
+          '.tab-content .no-match');
       assertTrue(!!noMatchMsg[0]);
       assertEquals(
           0,
           app.shadowRoot!
               .querySelectorAll(
-                  `#tab-content-available flags-experiment:not(.hidden)`)
+                  `#tab-content-available flags-experiment:not([hidden])`)
               .length);
       assertTrue(!!noMatchMsg[1]);
       assertEquals(
           0,
           app.shadowRoot!
               .querySelectorAll(
-                  `#tab-content-unavailable flags-experiment:not(.hidden)`)
+                  `#tab-content-unavailable flags-experiment:not([hidden])`)
               .length);
     });
   });
diff --git a/chrome/test/data/webui/lens/overlay/translate_button_test.ts b/chrome/test/data/webui/lens/overlay/translate_button_test.ts
index 93b4688..64b2edc 100644
--- a/chrome/test/data/webui/lens/overlay/translate_button_test.ts
+++ b/chrome/test/data/webui/lens/overlay/translate_button_test.ts
@@ -590,4 +590,143 @@
         1,
         testBrowserProxy.handler.getCallCount('issueTranslateFullPageRequest'));
   });
+
+  test('TranslateTabAndFocusOrder', async () => {
+    // Verify translate mode is disabled.
+    assertFalse(isRendered(overlayTranslateButtonElement.$.languagePicker));
+    assertFalse(
+        isRendered(overlayTranslateButtonElement.$.sourceLanguageButton));
+    assertFalse(
+        isRendered(overlayTranslateButtonElement.$.targetLanguageButton));
+
+    // Only the enable button should be tabbable.
+    assertEquals(
+        0, overlayTranslateButtonElement.$.translateEnableButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.translateDisableButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.sourceLanguageButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.targetLanguageButton.tabIndex);
+
+    // Click the translate button to show the language picker.
+    overlayTranslateButtonElement.$.translateEnableButton.click();
+    await waitAfterNextRender(overlayTranslateButtonElement);
+
+    // After clicking the translate button the source language button should
+    // have focus and the enable button should not be tabbable.
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.translateEnableButton.tabIndex);
+    assertEquals(
+        0, overlayTranslateButtonElement.$.translateDisableButton.tabIndex);
+    assertEquals(
+        0, overlayTranslateButtonElement.$.sourceLanguageButton.tabIndex);
+    assertEquals(
+        0, overlayTranslateButtonElement.$.targetLanguageButton.tabIndex);
+    assertEquals(
+        overlayTranslateButtonElement.shadowRoot!.activeElement,
+        overlayTranslateButtonElement.$.sourceLanguageButton);
+
+    // Clicking the source language button should open the picker menu.
+    overlayTranslateButtonElement.$.sourceLanguageButton.click();
+    await waitAfterNextRender(overlayTranslateButtonElement);
+    // The source language picker menu is visible.
+    assertTrue(
+        isVisible(overlayTranslateButtonElement.$.sourceLanguagePickerMenu));
+
+    // None of the language picker buttons should be tabbable.
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.translateEnableButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.translateDisableButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.sourceLanguageButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.targetLanguageButton.tabIndex);
+
+    // The back button in the source language picker menu should have focus.
+    assertEquals(
+        overlayTranslateButtonElement.shadowRoot!.activeElement,
+        overlayTranslateButtonElement.$.sourceLanguagePickerBackButton);
+
+    // Clicking the back button should close the picker menu and return focus to
+    // the source language button.
+    overlayTranslateButtonElement.$.sourceLanguagePickerBackButton.click();
+    await waitAfterNextRender(overlayTranslateButtonElement);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.translateEnableButton.tabIndex);
+    assertEquals(
+        0, overlayTranslateButtonElement.$.translateDisableButton.tabIndex);
+    assertEquals(
+        0, overlayTranslateButtonElement.$.sourceLanguageButton.tabIndex);
+    assertEquals(
+        0, overlayTranslateButtonElement.$.targetLanguageButton.tabIndex);
+    assertEquals(
+        overlayTranslateButtonElement.shadowRoot!.activeElement,
+        overlayTranslateButtonElement.$.sourceLanguageButton);
+
+    // Clicking the target language button should open the picker menu.
+    overlayTranslateButtonElement.$.targetLanguageButton.click();
+    await waitAfterNextRender(overlayTranslateButtonElement);
+    // The target language picker menu is visible.
+    assertTrue(
+        isVisible(overlayTranslateButtonElement.$.targetLanguagePickerMenu));
+
+    // None of the language picker buttons should be tabbable.
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.translateEnableButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.translateDisableButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.sourceLanguageButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.targetLanguageButton.tabIndex);
+    // The back button in the target language picker menu should have focus.
+    assertEquals(
+        overlayTranslateButtonElement.shadowRoot!.activeElement,
+        overlayTranslateButtonElement.$.targetLanguagePickerBackButton);
+
+    // Clicking the back button should close the picker menu and return focus to
+    // the target language button.
+    overlayTranslateButtonElement.$.targetLanguagePickerBackButton.click();
+    await waitAfterNextRender(overlayTranslateButtonElement);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.translateEnableButton.tabIndex);
+    assertEquals(
+        0, overlayTranslateButtonElement.$.translateDisableButton.tabIndex);
+    assertEquals(
+        0, overlayTranslateButtonElement.$.sourceLanguageButton.tabIndex);
+    assertEquals(
+        0, overlayTranslateButtonElement.$.targetLanguageButton.tabIndex);
+    assertEquals(
+        overlayTranslateButtonElement.shadowRoot!.activeElement,
+        overlayTranslateButtonElement.$.targetLanguageButton);
+
+    // Clicking the translate disable button should close translate mode and
+    // return focus to the translate enable button.
+    overlayTranslateButtonElement.$.translateDisableButton.click();
+    await waitAfterNextRender(overlayTranslateButtonElement);
+
+    // Verify translate mode is disabled.
+    assertFalse(isRendered(overlayTranslateButtonElement.$.languagePicker));
+    assertFalse(
+        isRendered(overlayTranslateButtonElement.$.sourceLanguageButton));
+    assertFalse(
+        isRendered(overlayTranslateButtonElement.$.targetLanguageButton));
+
+    // Only the enable button should be tabbable again.
+    assertEquals(
+        0, overlayTranslateButtonElement.$.translateEnableButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.translateDisableButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.sourceLanguageButton.tabIndex);
+    assertEquals(
+        -1, overlayTranslateButtonElement.$.targetLanguageButton.tabIndex);
+
+    // Verify focus on the translate enable button.
+    assertEquals(
+        overlayTranslateButtonElement.shadowRoot!.activeElement,
+        overlayTranslateButtonElement.$.translateEnableButton);
+  });
 });
diff --git a/chrome/test/data/webui/new_tab_page/app_test.ts b/chrome/test/data/webui/new_tab_page/app_test.ts
index 58eba12..e94a2e7 100644
--- a/chrome/test/data/webui/new_tab_page/app_test.ts
+++ b/chrome/test/data/webui/new_tab_page/app_test.ts
@@ -192,7 +192,7 @@
       // Act.
 
       // Create a dark mode theme with a custom background.
-      const theme = createTheme(true);
+      const theme = createTheme({isDark: true});
       theme.backgroundImage = createBackgroundImage('https://foo.com');
       callbackRouterRemote.setTheme(theme);
       await callbackRouterRemote.$.flushForTesting();
@@ -226,7 +226,7 @@
       // Arrange.
 
       // Set theme that triggers the scrim.
-      const theme = createTheme(true);
+      const theme = createTheme({isDark: true});
       theme.backgroundImage = createBackgroundImage('https://foo.com');
       callbackRouterRemote.setTheme(theme);
       await callbackRouterRemote.$.flushForTesting();
@@ -401,7 +401,7 @@
             // Act.
 
             // Create a theme with a custom background.
-            const theme = createTheme(isDark);
+            const theme = createTheme({isDark: isDark});
             theme.backgroundImage = createBackgroundImage('https://foo.com');
             callbackRouterRemote.setTheme(theme);
             await callbackRouterRemote.$.flushForTesting();
@@ -1046,7 +1046,7 @@
                 $$(app, '#customizeButton .customize-icon')!,
                 'background-color', 'rgb(255, 255, 255)');
 
-            const theme = createTheme(true);
+            const theme = createTheme({isDark: true});
             theme.backgroundImage = createBackgroundImage('https://foo.com');
             callbackRouterRemote.setTheme(theme);
             await callbackRouterRemote.$.flushForTesting();
@@ -1276,7 +1276,7 @@
                 32, $$<HTMLElement>(app, '#customizeButton')!.offsetWidth);
 
             // Create and set theme.
-            const theme = createTheme(true);
+            const theme = createTheme({isDark: true});
             theme.backgroundImage = createBackgroundImage('https://foo.com');
             callbackRouterRemote.setTheme(theme);
             await callbackRouterRemote.$.flushForTesting();
@@ -1401,9 +1401,7 @@
             assertTrue(
                 !!app.shadowRoot!.querySelector('#wallpaperSearchButton'));
 
-
-            // Set theme with a color but no background image.
-            callbackRouterRemote.setTheme(createTheme());
+            callbackRouterRemote.setTheme(createTheme({isBaseline: false}));
             await callbackRouterRemote.$.flushForTesting();
             await microtasksFinished();
 
diff --git a/chrome/test/data/webui/new_tab_page/test_support.ts b/chrome/test/data/webui/new_tab_page/test_support.ts
index 4729795..a2df9a3 100644
--- a/chrome/test/data/webui/new_tab_page/test_support.ts
+++ b/chrome/test/data/webui/new_tab_page/test_support.ts
@@ -67,7 +67,7 @@
   };
 }
 
-export function createTheme(isDark: boolean = false): Theme {
+export function createTheme({isDark = false, isBaseline = true} = {}): Theme {
   const mostVisited = {
     backgroundColor: {value: 0xff00ff00},
     isDark,
@@ -82,7 +82,7 @@
     dailyRefreshEnabled: false,
     backgroundImageCollectionId: '',
     logoColor: null,
-    isBaseline: true,
+    isBaseline: isBaseline,
     isDark,
     mostVisited: mostVisited,
     textColor: {value: 0xff0000ff},
diff --git a/chrome/test/interaction/interactive_browser_test.cc b/chrome/test/interaction/interactive_browser_test.cc
index 6ae3aeb..3c7155ee 100644
--- a/chrome/test/interaction/interactive_browser_test.cc
+++ b/chrome/test/interaction/interactive_browser_test.cc
@@ -781,10 +781,10 @@
       break;
   }
 
-  const bool shift = modifiers & ui_controls::AcceleratorState::kShift;
-  const bool alt = modifiers & ui_controls::AcceleratorState::kAlt;
-  const bool ctrl = modifiers & ui_controls::AcceleratorState::kControl;
-  const bool meta = modifiers & ui_controls::AcceleratorState::kCommand;
+  const bool shift = modifiers & ui_controls::kShift;
+  const bool alt = modifiers & ui_controls::kAlt;
+  const bool ctrl = modifiers & ui_controls::kControl;
+  const bool meta = modifiers & ui_controls::kCommand;
 
   auto b2s = [](bool b) { return b ? "true" : "false"; };
 
diff --git a/chrome/test/interaction/interactive_browser_test.h b/chrome/test/interaction/interactive_browser_test.h
index 75c1eba7..98cd097 100644
--- a/chrome/test/interaction/interactive_browser_test.h
+++ b/chrome/test/interaction/interactive_browser_test.h
@@ -371,8 +371,7 @@
       ui::ElementIdentifier web_contents,
       const DeepQuery& where,
       ui_controls::MouseButton button = ui_controls::LEFT,
-      ui_controls::AcceleratorState modifiers =
-          ui_controls::AcceleratorState::kNoAccelerator);
+      ui_controls::AcceleratorState modifiers = ui_controls::kNoAccelerator);
 
  protected:
   explicit InteractiveBrowserTestApi(
diff --git a/chrome/test/interaction/interactive_browser_test_browsertest.cc b/chrome/test/interaction/interactive_browser_test_browsertest.cc
index 9e32acd8..58075cd 100644
--- a/chrome/test/interaction/interactive_browser_test_browsertest.cc
+++ b/chrome/test/interaction/interactive_browser_test_browsertest.cc
@@ -32,6 +32,7 @@
 
 namespace {
 DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsId);
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContents2Id);
 constexpr char kDocumentWithNamedElement[] = "/select.html";
 constexpr char kDocumentWithLinks[] = "/links.html";
 constexpr char kDocumentWithClickDetection[] = "/click.html";
@@ -584,19 +585,16 @@
         testing::Values(ui_controls::LEFT,
                         ui_controls::MIDDLE,
                         ui_controls::RIGHT),
-        testing::Values(ui_controls::AcceleratorState::kNoAccelerator,
-                        ui_controls::AcceleratorState::kShift,
-                        ui_controls::AcceleratorState::kControl,
-                        ui_controls::AcceleratorState::kAlt,
-                        ui_controls::AcceleratorState::kCommand,
+        testing::Values(ui_controls::kNoAccelerator,
+                        ui_controls::kShift,
+                        ui_controls::kControl,
+                        ui_controls::kAlt,
+                        ui_controls::kCommand,
                         static_cast<ui_controls::AcceleratorState>(
-                            ui_controls::AcceleratorState::kAlt |
-                            ui_controls::AcceleratorState::kShift),
+                            ui_controls::kAlt | ui_controls::kShift),
                         static_cast<ui_controls::AcceleratorState>(
-                            ui_controls::AcceleratorState::kControl |
-                            ui_controls::AcceleratorState::kCommand |
-                            ui_controls::AcceleratorState::kAlt |
-                            ui_controls::AcceleratorState::kShift))),
+                            ui_controls::kControl | ui_controls::kCommand |
+                            ui_controls::kAlt | ui_controls::kShift))),
     [](const testing::TestParamInfo<ClickElementParams>& params) {
       std::ostringstream oss;
       switch (std::get<0>(params.param)) {
@@ -611,16 +609,16 @@
           break;
       }
       const auto accel = std::get<1>(params.param);
-      if (accel & ui_controls::AcceleratorState::kControl) {
+      if (accel & ui_controls::kControl) {
         oss << "_Control";
       }
-      if (accel & ui_controls::AcceleratorState::kAlt) {
+      if (accel & ui_controls::kAlt) {
         oss << "_Alt";
       }
-      if (accel & ui_controls::AcceleratorState::kShift) {
+      if (accel & ui_controls::kShift) {
         oss << "_Shift";
       }
-      if (accel & ui_controls::AcceleratorState::kCommand) {
+      if (accel & ui_controls::kCommand) {
         oss << "_Meta";
       }
       return oss.str();
@@ -637,7 +635,7 @@
       CheckJsResultAt(kWebContentsId, kButton, "el => el.lastClickEvent.button",
                       static_cast<int>(mouse_button)),
       CheckJsResultAt(kWebContentsId, kButton, "el => el.lastClickEvent.altKey",
-                      (modifier & ui_controls::AcceleratorState::kAlt) != 0),
+                      (modifier & ui_controls::kAlt) != 0),
       CheckJsResultAt(kWebContentsId, kButton,
                       "el => el.lastClickEvent.shiftKey",
                       (modifier & ui_controls::AcceleratorState::kShift) != 0),
@@ -659,6 +657,70 @@
           )"));
 }
 
+IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestClickElementTest,
+                       ClickElementOpensLink) {
+  const GURL url = embedded_test_server()->GetURL(kDocumentWithClickDetection);
+  const GURL url2 = embedded_test_server()->GetURL(kDocumentWithLinks);
+  const DeepQuery kLink = {"#link"};
+  RunTestSequence(InstrumentTab(kWebContentsId),
+                  NavigateWebContents(kWebContentsId, url),
+                  ClickElement(kWebContentsId, kLink),
+                  WaitForWebContentsNavigation(kWebContentsId, url2));
+}
+
+IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestClickElementTest,
+                       MiddleClickElementOpensLink) {
+  const GURL url = embedded_test_server()->GetURL(kDocumentWithClickDetection);
+  const GURL url2 = embedded_test_server()->GetURL(kDocumentWithLinks);
+  const DeepQuery kLink = {"#link"};
+  RunTestSequence(InstrumentTab(kWebContentsId),
+                  NavigateWebContents(kWebContentsId, url),
+                  InstrumentNextTab(kWebContents2Id),
+                  ClickElement(kWebContentsId, kLink, ui_controls::MIDDLE),
+                  WaitForWebContentsReady(kWebContents2Id, url2));
+}
+
+IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestClickElementTest,
+                       ControlClickElementOpensLink) {
+  const GURL url = embedded_test_server()->GetURL(kDocumentWithClickDetection);
+  const GURL url2 = embedded_test_server()->GetURL(kDocumentWithLinks);
+  const DeepQuery kLink = {"#link"};
+  RunTestSequence(InstrumentTab(kWebContentsId),
+                  NavigateWebContents(kWebContentsId, url),
+                  InstrumentNextTab(kWebContents2Id),
+                  ClickElement(kWebContentsId, kLink, ui_controls::LEFT,
+#if BUILDFLAG(IS_MAC)
+                               ui_controls::kCommand
+#else
+                               ui_controls::kControl
+#endif
+                               ),
+                  WaitForWebContentsReady(kWebContents2Id, url2));
+}
+
+IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestClickElementTest,
+                       ShiftClickElementOpensLink) {
+  const GURL url = embedded_test_server()->GetURL(kDocumentWithClickDetection);
+  const GURL url2 = embedded_test_server()->GetURL(kDocumentWithLinks);
+  const DeepQuery kLink = {"#link"};
+  RunTestSequence(
+      InstrumentTab(kWebContentsId), NavigateWebContents(kWebContentsId, url),
+      InstrumentNextTab(kWebContents2Id, AnyBrowser()),
+      ClickElement(kWebContentsId, kLink, ui_controls::LEFT,
+#if BUILDFLAG(IS_MAC)
+                   static_cast<ui_controls::AcceleratorState>(
+                       ui_controls::kCommand | ui_controls::kAlt)
+#else
+                   ui_controls::kShift
+#endif
+                       ),
+      InAnyContext(WaitForWebContentsReady(kWebContents2Id, url2)),
+      InSameContext(CheckView(
+          kBrowserViewElementId,
+          [](BrowserView* browser_view) { return browser_view->browser(); },
+          testing::Ne(browser()))));
+}
+
 // Parameter for WebUI coverage tests.
 struct CoverageConfig {
   // Whether to set the --devtools-code-coverage flag. If it's not set, nothing
diff --git a/chrome/updater/internal b/chrome/updater/internal
index 11f83f2..e8e0e06 160000
--- a/chrome/updater/internal
+++ b/chrome/updater/internal
@@ -1 +1 @@
-Subproject commit 11f83f25bbbd11d51c661f39e0cda7e6d7d2bada
+Subproject commit e8e0e06cc5b990d06b4583886f0f8946241fb221
diff --git a/chrome/updater/net/network_fetcher_mac.mm b/chrome/updater/net/network_fetcher_mac.mm
index 25959ea..5710409 100644
--- a/chrome/updater/net/network_fetcher_mac.mm
+++ b/chrome/updater/net/network_fetcher_mac.mm
@@ -56,15 +56,6 @@
 using DownloadToFileCompleteCallback =
     update_client::NetworkFetcher::DownloadToFileCompleteCallback;
 
-namespace {
-
-base::span<const uint8_t> AsByteSpan(NSData* data) {
-  return base::span<const uint8_t>(static_cast<const uint8_t*>(data.bytes),
-                                   data.length);
-}
-
-}  // namespace
-
 @interface CRUUpdaterNetworkController : NSObject <NSURLSessionDelegate>
 - (instancetype)initWithResponseStartedCallback:
                     (ResponseStartedCallback)responseStartedCallback
@@ -338,7 +329,7 @@
 - (void)URLSession:(NSURLSession*)session
           dataTask:(NSURLSessionDataTask*)dataTask
     didReceiveData:(NSData*)data {
-  if (_output.WriteAtCurrentPosAndCheck(AsByteSpan(data))) {
+  if (_output.WriteAtCurrentPosAndCheck(base::apple::NSDataToSpan(data))) {
     _callbackRunner->PostTask(
         FROM_HERE, base::BindOnce(_progressCallback, _output.GetLength()));
     [dataTask resume];
diff --git a/chrome/updater/win/installer/BUILD.gn b/chrome/updater/win/installer/BUILD.gn
index efda930..9ed0fd3 100644
--- a/chrome/updater/win/installer/BUILD.gn
+++ b/chrome/updater/win/installer/BUILD.gn
@@ -76,6 +76,7 @@
   visibility = [ ":*" ]
   sources = [ "installer.rc" ]
   public_deps = [ ":lib" ]
+  deps = [ "//build:branding_buildflags" ]
 }
 
 # `process_version_rc_template` invokes `process_version` with the default
diff --git a/chrome/updater/win/installer/installer.rc b/chrome/updater/win/installer/installer.rc
index 70f8f2b..246c9ec8 100644
--- a/chrome/updater/win/installer/installer.rc
+++ b/chrome/updater/win/installer/installer.rc
@@ -1,5 +1,6 @@
 // Microsoft Visual C++ generated resource script.
 //
+#include "build/branding_buildflags.h"
 #include "installer_resource.h"
 
 #define APSTUDIO_READONLY_SYMBOLS
@@ -29,9 +30,14 @@
 
 // Icon with lowest ID value placed first to ensure application icon
 // remains consistent on all systems.
-IDI_MINI_INSTALLER      ICON                    "installer.ico"
+IDI_MINI_INSTALLER      ICON                "installer.ico"
 
-IDB_LOGO         BITMAP                         "logo.bmp"
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  IDB_LOGO         BITMAP                   "chrome/updater/internal/logo.bmp"
+#else
+  IDB_LOGO         BITMAP                   "logo.bmp"
+#endif
+
 
 #ifdef APSTUDIO_INVOKED
 /////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/updater/win/installer/logo.bmp b/chrome/updater/win/installer/logo.bmp
index ded1ab6..bd791d0 100644
--- a/chrome/updater/win/installer/logo.bmp
+++ b/chrome/updater/win/installer/logo.bmp
Binary files differ
diff --git a/chromecast/BUILD.gn b/chromecast/BUILD.gn
index fe40aad..4c896755 100644
--- a/chromecast/BUILD.gn
+++ b/chromecast/BUILD.gn
@@ -656,15 +656,15 @@
     min_sdk_version = 24
     target_sdk_version = 33
 
-    shared_libraries = [ "//chromecast/android:libcast_shell_android" ]
+    shared_libraries = [ "//chromecast/android:libcast_browser_android" ]
     srcjar_deps =
-        [ "//chromecast/android:libcast_shell_android__jni_registration" ]
+        [ "//chromecast/android:libcast_browser_android__jni_registration" ]
 
     deps = [
       ":cast_browser_apk_assets",
       "//base:base_java",
       "//build/android:build_java",
-      "//chromecast/android:libcast_shell_android",
+      "//chromecast/android:libcast_browser_android",
       "//chromecast/browser/android:cast_shell_java",
       "//components/crash/core/app:chrome_crashpad_handler_named_as_so",
       "//third_party/jni_zero:jni_zero_java",
diff --git a/chromecast/android/BUILD.gn b/chromecast/android/BUILD.gn
index 645b78ea..0367c43 100644
--- a/chromecast/android/BUILD.gn
+++ b/chromecast/android/BUILD.gn
@@ -11,57 +11,6 @@
 # These targets shall only be referenced on Android builds.
 assert(is_android)
 
-# Deps shared by libcast_browser_android and libcast_shell_android.
-group("common_apk_deps") {
-  public_deps = [
-    "//base",
-    "//chromecast:chromecast_buildflags",
-    "//chromecast/app",
-    "//chromecast/app:cast_crash_client",
-    "//chromecast/base",
-    "//chromecast/base:android_create_sys_info",
-    "//chromecast/base:jni_headers",
-    "//chromecast/base/metrics",
-    "//chromecast/browser",
-    "//chromecast/media/cma/backend/android:cast_media_android",
-    "//components/crash/android:crash_android",
-    "//components/minidump_uploader",
-    "//components/module_installer/android:native",
-    "//content/public/app",
-    "//content/public/browser",
-    "//skia",
-  ]
-
-  # Explicit dependencies required for JNI registration to be able to find the
-  # native side functions.
-  if (is_component_build) {
-    public_deps += [
-      "//device/bluetooth",
-      "//device/gamepad",
-      "//media/midi",
-      "//ui/android",
-      "//ui/events/devices",
-      "//ui/shell_dialogs",
-    ]
-  }
-}
-
-shared_library_with_jni("libcast_shell_android") {
-  # TODO: Remove the ldflags after migrating away from protobuf_lite to
-  # protobuf_full.
-  ldflags = [ "-Wl,-z,muldefs" ]
-
-  sources = [ "//chromecast/app/android/cast_jni_loader.cc" ]
-  configs += [ "//chromecast:cast_config" ]
-  configs -= [ "//build/config/android:hide_all_but_jni_onload" ]
-
-  deps = [
-    ":common_apk_deps",
-    "//chromecast/cast_core:core_runtime_lib_simple",
-  ]
-  java_targets = [ "//chromecast:cast_browser_apk" ]
-}
-
 shared_library_with_jni("libcast_browser_android") {
   # TODO: Remove the ldflags after migrating away from protobuf_lite to
   # protobuf_full.
@@ -72,33 +21,28 @@
   configs -= [ "//build/config/android:hide_all_but_jni_onload" ]
 
   deps = [
-    ":common_apk_deps",
+    "//base",
+    "//chromecast:chromecast_buildflags",
+    "//chromecast/app",
+    "//chromecast/app:cast_crash_client",
+    "//chromecast/base",
+    "//chromecast/base:android_create_sys_info",
+    "//chromecast/base:jni_headers",
+    "//chromecast/base/metrics",
+    "//chromecast/browser",
     "//chromecast/cast_core:core_runtime_lib_simple",
+    "//chromecast/media/cma/backend/android:cast_media_android",
+    "//components/crash/android:crash_android",
+    "//components/minidump_uploader",
     "//components/module_installer/android:native",
+    "//content/public/app",
+    "//content/public/browser",
+    "//skia",
   ]
+
   java_targets = [ "//chromecast:cast_browser_apk" ]
 }
 
-group("native") {
-  deps = [ ":cast_browser" ]
-}
-
-component("cast_browser") {
-  # TODO: Remove the ldflags after migrating away from protobuf_lite to
-  # protobuf_full.
-  ldflags = [ "-Wl,-z,muldefs" ]
-
-  sources = [ "//chromecast/app/android/cast_browser_module_entrypoint.cc" ]
-
-  deps = [
-    ":common_apk_deps",
-    "//chromecast:cast_shell_lib_simple",
-    "//third_party/jni_zero",
-  ]
-
-  cflags = [ "-fsymbol-partition=cast_browser_partition" ]
-}
-
 # These are all known //third_party/android_deps targets that chromecast
 # internal code depends on. Reference these targets so if anyone wants to
 # remove these targets upstream, they need to at least add someone from
diff --git a/chromecast/browser/BUILD.gn b/chromecast/browser/BUILD.gn
index 5b5d5ad..7596f5f 100644
--- a/chromecast/browser/BUILD.gn
+++ b/chromecast/browser/BUILD.gn
@@ -319,6 +319,7 @@
       "android/cast_web_service_android.cc",
     ]
     deps += [
+      "//components/android_autofill/browser:android",
       "//chromecast/browser/android:jni_headers",
       "//components/embedder_support/android:view",
     ]
diff --git a/chromecast/cast_core/grpc/grpc_server_streaming_test.cc b/chromecast/cast_core/grpc/grpc_server_streaming_test.cc
index 4271180..62c152b 100644
--- a/chromecast/cast_core/grpc/grpc_server_streaming_test.cc
+++ b/chromecast/cast_core/grpc/grpc_server_streaming_test.cc
@@ -271,9 +271,12 @@
         ASSERT_THAT(status, StatusIs(grpc::StatusCode::ABORTED));
         reactor_aborted.Signal();
       }));
+  // The handler might have already been called by gRPC framework, so write an
+  // empty message to release its deferred state.
+  cancelled_reactor->Write(TestResponse());
+
   ASSERT_TRUE(reactor_aborted.TimedWait(kEventTimeout));
 
-  cancelled_reactor->Write(TestResponse());
   test::StopGrpcServer(server, kServerStopTimeout);
 }
 
diff --git a/chromecast/media/audio/cast_audio_manager_android.h b/chromecast/media/audio/cast_audio_manager_android.h
index 4178292..75fdab0d 100644
--- a/chromecast/media/audio/cast_audio_manager_android.h
+++ b/chromecast/media/audio/cast_audio_manager_android.h
@@ -12,8 +12,7 @@
 #include "chromecast/media/audio/cast_audio_manager_helper.h"
 #include "media/audio/android/audio_manager_android.h"
 
-namespace chromecast {
-namespace media {
+namespace chromecast::media {
 
 class CastAudioManagerAndroid : public ::media::AudioManagerAndroid {
  public:
@@ -25,6 +24,9 @@
       scoped_refptr<base::SingleThreadTaskRunner> media_task_runner);
   ~CastAudioManagerAndroid() override;
 
+  CastAudioManagerAndroid(const CastAudioManagerAndroid&) = delete;
+  CastAudioManagerAndroid& operator=(const CastAudioManagerAndroid&) = delete;
+
   // AudioManager implementation.
   void GetAudioOutputDeviceNames(
       ::media::AudioDeviceNames* device_names) override;
@@ -49,7 +51,12 @@
   ::media::AudioParameters GetInputStreamParameters(
       const std::string& device_id) override;
 
+  // Make this public for testing.
+  using ::media::AudioManagerBase::GetOutputStreamParameters;
+
  private:
+  friend class CastAudioManagerTest;
+
   // CastAudioManager implementation.
   ::media::AudioInputStream* MakeLinearInputStream(
       const ::media::AudioParameters& params,
@@ -61,12 +68,8 @@
       const ::media::AudioManager::LogCallback& log_callback) override;
 
   CastAudioManagerHelper helper_;
-
-  CastAudioManagerAndroid(const CastAudioManagerAndroid&) = delete;
-  CastAudioManagerAndroid& operator=(const CastAudioManagerAndroid&) = delete;
 };
 
-}  // namespace media
-}  // namespace chromecast
+}  // namespace chromecast::media
 
 #endif  // CHROMECAST_MEDIA_AUDIO_CAST_AUDIO_MANAGER_ANDROID_H_
diff --git a/chromecast/media/audio/cast_audio_manager_unittest.cc b/chromecast/media/audio/cast_audio_manager_unittest.cc
index b648513..e2ce0ac 100644
--- a/chromecast/media/audio/cast_audio_manager_unittest.cc
+++ b/chromecast/media/audio/cast_audio_manager_unittest.cc
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chromecast/media/audio/cast_audio_manager.h"
-
 #include <memory>
 #include <string>
 #include <utility>
@@ -25,8 +23,12 @@
 #include "media/media_buildflags.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+
 #if BUILDFLAG(IS_ANDROID)
+#include "chromecast/media/audio/cast_audio_manager_android.h"
 #include "media/audio/android/audio_track_output_stream.h"
+#else  // BUILDFLAG(IS_ANDROID)
+#include "chromecast/media/audio/cast_audio_manager.h"
 #endif  // BUILDFLAG(IS_ANDROID)
 
 using testing::_;
@@ -60,8 +62,7 @@
 
 }  // namespace
 
-namespace chromecast {
-namespace media {
+namespace chromecast::media {
 
 class CastAudioManagerTest : public testing::Test {
  public:
@@ -96,6 +97,14 @@
     CHECK(audio_thread_.StartAndWaitForTesting());
 
     mock_backend_factory_ = std::make_unique<MockCmaBackendFactory>();
+#if BUILDFLAG(IS_ANDROID)
+    audio_manager_ = std::make_unique<CastAudioManagerAndroid>(
+        std::make_unique<::media::TestAudioThread>(), &fake_audio_log_factory_,
+        &mock_delegate_,
+        base::BindRepeating(&CastAudioManagerTest::GetCmaBackendFactory,
+                            base::Unretained(this)),
+        task_environment_.GetMainThreadTaskRunner());
+#else   // BUILDFLAG(IS_ANDROID)
     audio_manager_ = base::WrapUnique(new CastAudioManager(
         std::make_unique<::media::TestAudioThread>(), &fake_audio_log_factory_,
         &mock_delegate_,
@@ -105,6 +114,7 @@
         audio_thread_.task_runner(), use_mixer,
         true /* force_use_cma_backend_for_output*/
         ));
+#endif  // BUILDFLAG(IS_ANDROID)
     // A few AudioManager implementations post initialization tasks to
     // audio thread. Flush the thread to ensure that |audio_manager_| is
     // initialized and ready to use before returning from this function.
@@ -116,6 +126,8 @@
   }
 
   void SetUpBackendAndDecoder() {
+#if !BUILDFLAG(IS_ANDROID)
+    // Android impl of CastAudioManager does not use CMA.
     mock_audio_decoder_ =
         std::make_unique<NiceMock<MockCmaBackend::AudioDecoder>>();
     EXPECT_CALL(*mock_audio_decoder_, SetDelegate(_)).Times(1);
@@ -130,6 +142,7 @@
         .WillOnce(Invoke([this](const MediaPipelineDeviceParams&) {
           return std::move(mock_cma_backend_);
         }));
+#endif  // !BUILDFLAG(IS_ANDROID)
     EXPECT_EQ(mock_backend_factory_.get(),
               audio_manager_->helper_.GetCmaBackendFactory());
   }
@@ -148,7 +161,12 @@
   std::unique_ptr<MockCmaBackend> mock_cma_backend_;
   std::unique_ptr<MockCmaBackend::AudioDecoder> mock_audio_decoder_;
 
+#if BUILDFLAG(IS_ANDROID)
+  std::unique_ptr<CastAudioManagerAndroid> audio_manager_;
+#else   // BUILDFLAG(IS_ANDROID)
   std::unique_ptr<CastAudioManager> audio_manager_;
+#endif  // BUILDFLAG(IS_ANDROID)
+
   std::unique_ptr<::media::AudioDeviceInfoAccessorForTests>
       device_info_accessor_;
 };
@@ -167,7 +185,9 @@
       kDefaultAudioParams, "", ::media::AudioManager::LogCallback());
   EXPECT_TRUE(stream->Open());
 
-  EXPECT_CALL(*mock_cma_backend_, Start(_)).WillOnce(Return(true));
+  if (mock_cma_backend_) {
+    EXPECT_CALL(*mock_cma_backend_, Start(_)).WillOnce(Return(true));
+  }
   EXPECT_CALL(mock_source_callback_, OnMoreData(_, _, _, _))
       .WillRepeatedly(Invoke(OnMoreData));
   EXPECT_CALL(mock_source_callback_, OnError(_)).Times(0);
@@ -189,7 +209,7 @@
       ::media::AudioParameters::kAudioCDSampleRate, 256);
   ::media::AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(
       kAC3AudioParams, "", ::media::AudioManager::LogCallback());
-  EXPECT_TRUE(stream);
+  ASSERT_TRUE(stream);
   // Only run the rest of the test if the device supports AC3.
   if (stream->Open()) {
     EXPECT_CALL(mock_source_callback_, OnMoreData(_, _, _, _))
@@ -212,7 +232,7 @@
       ::media::AudioParameters::kAudioCDSampleRate, 256);
   ::media::AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(
       kDTSAudioParams, "", ::media::AudioManager::LogCallback());
-  EXPECT_TRUE(stream);
+  ASSERT_TRUE(stream);
   // Only run the rest of the test if the device supports DTS.
   if (stream->Open()) {
     EXPECT_CALL(mock_source_callback_, OnMoreData(_, _, _, _))
@@ -233,9 +253,13 @@
   SetUpBackendAndDecoder();
   ::media::AudioOutputStream* stream =
       audio_manager_->MakeAudioOutputStreamProxy(kDefaultAudioParams, "");
+  ASSERT_TRUE(stream);
   EXPECT_TRUE(stream->Open());
   RunThreadsUntilIdle();
-  EXPECT_CALL(*mock_cma_backend_, Start(_)).WillOnce(Return(true));
+
+  if (mock_cma_backend_) {
+    EXPECT_CALL(*mock_cma_backend_, Start(_)).WillOnce(Return(true));
+  }
   EXPECT_CALL(mock_source_callback_, OnMoreData(_, _, _, _))
       .WillRepeatedly(Invoke(OnMoreData));
   EXPECT_CALL(mock_source_callback_, OnError(_)).Times(0);
@@ -255,12 +279,16 @@
   SetUpBackendAndDecoder();
   ::media::AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(
       kDefaultAudioParams, "", ::media::AudioManager::LogCallback());
+  ASSERT_TRUE(stream);
   EXPECT_TRUE(stream->Open());
 
-  EXPECT_CALL(*mock_cma_backend_, Start(_)).WillOnce(Return(true));
+  if (mock_cma_backend_) {
+    EXPECT_CALL(*mock_cma_backend_, Start(_)).WillOnce(Return(true));
+  }
   EXPECT_CALL(mock_source_callback_, OnMoreData(_, _, _, _))
       .WillRepeatedly(Invoke(OnMoreData));
   EXPECT_CALL(mock_source_callback_, OnError(_)).Times(0);
+
   stream->Start(&mock_source_callback_);
   RunThreadsUntilIdle();
 
@@ -291,5 +319,4 @@
   stream->Close();
 }
 
-}  // namespace media
-}  // namespace chromecast
+}  // namespace chromecast::media
diff --git a/chromecast/media/cma/BUILD.gn b/chromecast/media/cma/BUILD.gn
index 32e1fe6..a2b3e72 100644
--- a/chromecast/media/cma/BUILD.gn
+++ b/chromecast/media/cma/BUILD.gn
@@ -48,7 +48,6 @@
   testonly = true
   sources = [
     "backend/audio_video_pipeline_device_unittest.cc",
-    "backend/multizone_backend_unittest.cc",
     "base/balanced_media_task_runner_unittest.cc",
     "base/buffering_controller_unittest.cc",
     "base/buffering_frame_provider_unittest.cc",
@@ -79,6 +78,9 @@
   # Android because both protobuf_full and protobuf_lite get included.
   if (!is_android) {
     sources += [
+      # For whatever reason the MZ backend tests take ~20sec to run in CI as opposed to
+      # 200ms when ran locally. So enable them only on non-Android envs.
+      "backend/multizone_backend_unittest.cc",
       "backend/proxy/audio_decoder_pipeline_node_unittest.cc",
       "backend/proxy/buffer_id_manager_unittest.cc",
       "backend/proxy/cma_backend_proxy_unittest.cc",
diff --git a/chromecast/media/cma/backend/mixer/stream_mixer_unittest.cc b/chromecast/media/cma/backend/mixer/stream_mixer_unittest.cc
index 4a55bfc..c6902de 100644
--- a/chromecast/media/cma/backend/mixer/stream_mixer_unittest.cc
+++ b/chromecast/media/cma/backend/mixer/stream_mixer_unittest.cc
@@ -368,7 +368,7 @@
 
 std::string DeathRegex(const std::string& regex) {
 // String arguments aren't passed to CHECK() in official builds.
-#if defined(OFFICIAL_BUILD) && defined(NDEBUG)
+#if BUILDFLAG(IS_ANDROID) || (defined(OFFICIAL_BUILD) && defined(NDEBUG))
   return "";
 #else
   return regex;
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index ce8a8656..c65ce442 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-16050.0.0-1063704
\ No newline at end of file
+16050.0.0-1063710
\ No newline at end of file
diff --git a/chromeos/SECURITY_OWNERS b/chromeos/SECURITY_OWNERS
index 33a4513..49c114bb 100644
--- a/chromeos/SECURITY_OWNERS
+++ b/chromeos/SECURITY_OWNERS
@@ -1,2 +1,4 @@
+hardikgoyal@chromium.org
+jadmanski@chromium.org
 jorgelo@chromium.org
-palmer@chromium.org
+lziest@google.com
diff --git a/chromeos/ash/components/boca/babelorca/BUILD.gn b/chromeos/ash/components/boca/babelorca/BUILD.gn
index 26e69b8..1e67b33 100644
--- a/chromeos/ash/components/boca/babelorca/BUILD.gn
+++ b/chromeos/ash/components/boca/babelorca/BUILD.gn
@@ -18,6 +18,7 @@
     "tachyon_authed_client.h",
     "tachyon_authed_client_impl.cc",
     "tachyon_authed_client_impl.h",
+    "tachyon_client.cc",
     "tachyon_client.h",
     "tachyon_client_impl.cc",
     "tachyon_client_impl.h",
@@ -27,6 +28,8 @@
     "tachyon_request_data_provider.h",
     "tachyon_response.cc",
     "tachyon_response.h",
+    "tachyon_streaming_client.cc",
+    "tachyon_streaming_client.h",
     "tachyon_utils.cc",
     "tachyon_utils.h",
     "token_data_wrapper.h",
@@ -43,10 +46,12 @@
     "//chromeos/ash/components/boca",
     "//chromeos/ash/components/boca/babelorca/proto",
     "//chromeos/ash/components/boca/proto",
+    "//chromeos/ash/services/boca/babelorca/mojom",
     "//components/signin/public/base",
     "//components/signin/public/identity_manager",
     "//google_apis",
     "//media/mojo/mojom:speech_recognition",
+    "//mojo/public/cpp/bindings",
     "//net",
     "//services/network/public/cpp",
   ]
@@ -85,6 +90,7 @@
     "tachyon_client_impl_unittest.cc",
     "tachyon_registrar_unittest.cc",
     "tachyon_response_unittest.cc",
+    "tachyon_streaming_client_unittest.cc",
     "token_manager_impl_unittest.cc",
     "transcript_sender_unittest.cc",
   ]
@@ -96,10 +102,12 @@
     "//base/test:test_support",
     "//chromeos/ash/components/boca/babelorca/proto",
     "//chromeos/ash/components/boca/babelorca/proto:test_support",
+    "//chromeos/ash/services/boca/babelorca/mojom",
     "//components/signin/public/base",
     "//components/signin/public/identity_manager:test_support",
     "//google_apis",
     "//media/mojo/mojom:speech_recognition",
+    "//mojo/public/cpp/bindings",
     "//net",
     "//net/traffic_annotation:test_support",
     "//services/network:test_support",
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_client.cc b/chromeos/ash/components/boca/babelorca/tachyon_client.cc
new file mode 100644
index 0000000..84907a1
--- /dev/null
+++ b/chromeos/ash/components/boca/babelorca/tachyon_client.cc
@@ -0,0 +1,40 @@
+// 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 "chromeos/ash/components/boca/babelorca/tachyon_client.h"
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "base/functional/callback.h"
+#include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_response.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace ash::babelorca {
+
+// static
+void TachyonClient::HandleResponse(
+    std::unique_ptr<network::SimpleURLLoader> url_loader,
+    std::unique_ptr<RequestDataWrapper> request_data,
+    AuthFailureCallback auth_failure_cb,
+    std::unique_ptr<std::string> response_body) {
+  std::optional<int> http_status_code =
+      url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers
+          ? std::make_optional(
+                url_loader->ResponseInfo()->headers->response_code())
+          : std::nullopt;
+  TachyonResponse response(url_loader->NetError(), http_status_code,
+                           std::move(response_body));
+  if (response.status() == TachyonResponse::Status::kAuthError) {
+    std::move(auth_failure_cb).Run(std::move(request_data));
+    return;
+  }
+  std::move(request_data->response_cb).Run(std::move(response));
+}
+
+}  // namespace ash::babelorca
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_client.h b/chromeos/ash/components/boca/babelorca/tachyon_client.h
index 34c0e0c..4c9475c 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_client.h
+++ b/chromeos/ash/components/boca/babelorca/tachyon_client.h
@@ -10,6 +10,10 @@
 
 #include "base/functional/callback_forward.h"
 
+namespace network {
+class SimpleURLLoader;
+}  // namespace network
+
 namespace ash::babelorca {
 
 struct RequestDataWrapper;
@@ -19,6 +23,12 @@
   using AuthFailureCallback =
       base::OnceCallback<void(std::unique_ptr<RequestDataWrapper>)>;
 
+  static void HandleResponse(
+      std::unique_ptr<network::SimpleURLLoader> url_loader,
+      std::unique_ptr<RequestDataWrapper> request_data,
+      AuthFailureCallback auth_failure_cb,
+      std::unique_ptr<std::string> response_body);
+
   TachyonClient(const TachyonClient&) = delete;
   TachyonClient& operator=(const TachyonClient&) = delete;
 
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_client_impl.cc b/chromeos/ash/components/boca/babelorca/tachyon_client_impl.cc
index 270b022..30e1b28c 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_client_impl.cc
+++ b/chromeos/ash/components/boca/babelorca/tachyon_client_impl.cc
@@ -15,12 +15,10 @@
 #include "base/memory/weak_ptr.h"
 #include "base/strings/stringprintf.h"
 #include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_constants.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_response.h"
 #include "net/base/load_flags.h"
-#include "net/base/net_errors.h"
 #include "net/http/http_request_headers.h"
-#include "net/http/http_status_code.h"
-#include "services/network/public/cpp/header_util.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
@@ -33,7 +31,6 @@
 
 // TODO(b/353974384): Identify an accurate max size.
 constexpr int kMaxResponseBodySize = 1024 * 1024;
-constexpr char kOauthHeaderTemplate[] = "Authorization: Bearer %s";
 
 }  // namespace
 
@@ -80,17 +77,8 @@
     std::unique_ptr<RequestDataWrapper> request_data,
     AuthFailureCallback auth_failure_cb,
     std::unique_ptr<std::string> response_body) {
-  std::optional<int> http_status_code;
-  if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers) {
-    http_status_code = url_loader->ResponseInfo()->headers->response_code();
-  }
-  TachyonResponse response(url_loader->NetError(), http_status_code,
-                           std::move(response_body));
-  if (response.status() == TachyonResponse::Status::kAuthError) {
-    std::move(auth_failure_cb).Run(std::move(request_data));
-    return;
-  }
-  std::move(request_data->response_cb).Run(std::move(response));
+  HandleResponse(std::move(url_loader), std::move(request_data),
+                 std::move(auth_failure_cb), std::move(response_body));
 }
 
 }  // namespace ash::babelorca
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_constants.h b/chromeos/ash/components/boca/babelorca/tachyon_constants.h
index 27ce6634..a3da5fed 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_constants.h
+++ b/chromeos/ash/components/boca/babelorca/tachyon_constants.h
@@ -15,6 +15,8 @@
 inline constexpr char kSendMessageUrl[] =
     "https://instantmessaging-pa.googleapis.com/v1/message:send";
 
+inline constexpr char kOauthHeaderTemplate[] = "Authorization: Bearer %s";
+
 }  // namespace ash::babelorca
 
 #endif  // CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_TACHYON_CONSTANTS_H_
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.cc b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.cc
new file mode 100644
index 0000000..da0a3eb1
--- /dev/null
+++ b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.cc
@@ -0,0 +1,145 @@
+// 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 "chromeos/ash/components/boca/babelorca/tachyon_streaming_client.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/check.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/strings/stringprintf.h"
+#include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_client.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_constants.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_response.h"
+#include "chromeos/ash/services/boca/babelorca/mojom/tachyon_parsing_service.mojom-shared.h"
+#include "chromeos/ash/services/boca/babelorca/mojom/tachyon_parsing_service.mojom.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/load_flags.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "url/gurl.h"
+
+namespace ash::babelorca {
+
+TachyonStreamingClient::TachyonStreamingClient(
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    ParsingServiceBinder binder_callback,
+    OnMessageCallback on_message_callback)
+    : url_loader_factory_(url_loader_factory),
+      binder_callback_(std::move(binder_callback)),
+      on_message_callback_(std::move(on_message_callback)) {}
+
+TachyonStreamingClient::~TachyonStreamingClient() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void TachyonStreamingClient::StartRequest(
+    std::unique_ptr<RequestDataWrapper> request_data,
+    std::string oauth_token,
+    AuthFailureCallback auth_failure_cb) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  request_data_ = std::move(request_data);
+  auth_failure_cb_ = std::move(auth_failure_cb);
+
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = GURL(request_data_->url);
+  resource_request->load_flags =
+      net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  resource_request->method = net::HttpRequestHeaders::kPostMethod;
+  resource_request->headers.AddHeaderFromString(
+      base::StringPrintf(kOauthHeaderTemplate, oauth_token.c_str()));
+
+  url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
+                                                 request_data_->annotation_tag);
+  if (request_data_->max_retries > 0) {
+    const int retry_mode = network::SimpleURLLoader::RETRY_ON_5XX |
+                           network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE;
+    url_loader_->SetRetryOptions(request_data_->max_retries, retry_mode);
+  }
+  url_loader_->AttachStringForUpload(request_data_->content_data,
+                                     "application/x-protobuf");
+  url_loader_->DownloadAsStream(url_loader_factory_.get(), this);
+}
+
+void TachyonStreamingClient::OnDataReceived(std::string_view string_piece,
+                                            base::OnceClosure resume) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!parsing_service_.is_bound()) {
+    parsing_service_ = binder_callback_.Run();
+    parsing_service_.set_disconnect_handler(
+        base::BindOnce(&TachyonStreamingClient::OnParsingServiceDisconnected,
+                       base::Unretained(this)));
+  }
+  parsing_service_->Parse(
+      std::string(string_piece),
+      base::BindOnce(&TachyonStreamingClient::OnParsed, base::Unretained(this),
+                     std::move(resume)));
+}
+
+void TachyonStreamingClient::OnComplete(bool success) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  parsing_service_.reset();
+  if (success) {
+    std::move(request_data_->response_cb)
+        .Run(TachyonResponse(TachyonResponse::Status::kOk));
+    return;
+  }
+  HandleResponse(std::move(url_loader_), std::move(request_data_),
+                 std::move(auth_failure_cb_), /*response_body=*/nullptr);
+}
+
+void TachyonStreamingClient::OnRetry(base::OnceClosure start_retry) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  parsing_service_.reset();
+  std::move(start_retry).Run();
+}
+
+void TachyonStreamingClient::OnParsed(
+    base::OnceClosure resume,
+    mojom::ParsingState parsing_state,
+    std::vector<mojom::BabelOrcaMessagePtr> messages,
+    mojom::StreamStatusPtr stream_status) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  for (auto& message : messages) {
+    on_message_callback_.Run(std::move(message));
+  }
+  if (parsing_state == mojom::ParsingState::kOk) {
+    std::move(resume).Run();
+    return;
+  }
+  url_loader_.reset();
+  parsing_service_.reset();
+  // Report internal error if there is a parsing error or the stream is closed
+  // and stream_status is not present.
+  if (parsing_state == mojom::ParsingState::kError || stream_status.is_null()) {
+    std::move(request_data_->response_cb)
+        .Run(TachyonResponse(TachyonResponse::Status::kInternalError));
+    return;
+  }
+  TachyonResponse response(stream_status->code, stream_status->message);
+  if (response.status() == TachyonResponse::Status::kAuthError) {
+    std::move(auth_failure_cb_).Run(std::move(request_data_));
+    return;
+  }
+  std::move(request_data_->response_cb)
+      .Run(TachyonResponse(stream_status->code, stream_status->message));
+}
+
+void TachyonStreamingClient::OnParsingServiceDisconnected() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  url_loader_.reset();
+  parsing_service_.reset();
+  std::move(request_data_->response_cb)
+      .Run(TachyonResponse(TachyonResponse::Status::kInternalError));
+}
+
+}  // namespace ash::babelorca
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.h b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.h
new file mode 100644
index 0000000..63f8eb7
--- /dev/null
+++ b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.h
@@ -0,0 +1,89 @@
+// 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 CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_TACHYON_STREAMING_CLIENT_H_
+#define CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_TACHYON_STREAMING_CLIENT_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/functional/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/thread_annotations.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_client.h"
+#include "chromeos/ash/services/boca/babelorca/mojom/tachyon_parsing_service.mojom-shared.h"
+#include "chromeos/ash/services/boca/babelorca/mojom/tachyon_parsing_service.mojom.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
+
+namespace network {
+
+class SharedURLLoaderFactory;
+class SimpleURLLoader;
+
+}  // namespace network
+
+namespace ash::babelorca {
+
+struct RequestDataWrapper;
+
+class TachyonStreamingClient : public TachyonClient,
+                               public network::SimpleURLLoaderStreamConsumer {
+ public:
+  using ParsingServiceBinder =
+      base::RepeatingCallback<mojo::Remote<mojom::TachyonParsingService>()>;
+  using OnMessageCallback =
+      base::RepeatingCallback<void(mojom::BabelOrcaMessagePtr)>;
+
+  TachyonStreamingClient(
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      ParsingServiceBinder binder_callback,
+      OnMessageCallback on_message_callback);
+
+  TachyonStreamingClient(const TachyonStreamingClient&) = delete;
+  TachyonStreamingClient& operator=(const TachyonStreamingClient&) = delete;
+
+  ~TachyonStreamingClient() override;
+
+  // TachyonClient:
+  void StartRequest(std::unique_ptr<RequestDataWrapper> request_data,
+                    std::string oauth_token,
+                    AuthFailureCallback auth_failure_cb) override;
+
+  // network::SimpleURLLoaderStreamConsumer:
+  void OnDataReceived(std::string_view string_piece,
+                      base::OnceClosure resume) override;
+  void OnComplete(bool success) override;
+  void OnRetry(base::OnceClosure start_retry) override;
+
+ private:
+  void OnParsed(base::OnceClosure resume,
+                mojom::ParsingState parsing_state,
+                std::vector<mojom::BabelOrcaMessagePtr> messages,
+                mojom::StreamStatusPtr stream_status);
+  void OnParsingServiceDisconnected();
+
+  const scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+  const ParsingServiceBinder binder_callback_;
+  const OnMessageCallback on_message_callback_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  std::unique_ptr<RequestDataWrapper> request_data_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  AuthFailureCallback auth_failure_cb_ GUARDED_BY_CONTEXT(sequence_checker_);
+
+  mojo::Remote<mojom::TachyonParsingService> parsing_service_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  std::unique_ptr<network::SimpleURLLoader> url_loader_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+};
+
+}  // namespace ash::babelorca
+
+#endif  // CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_TACHYON_STREAMING_CLIENT_H_
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_streaming_client_unittest.cc b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client_unittest.cc
new file mode 100644
index 0000000..32d179c
--- /dev/null
+++ b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client_unittest.cc
@@ -0,0 +1,308 @@
+// 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 "chromeos/ash/components/boca/babelorca/tachyon_streaming_client.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/repeating_test_future.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_response.h"
+#include "chromeos/ash/services/boca/babelorca/mojom/tachyon_parsing_service.mojom-shared.h"
+#include "chromeos/ash/services/boca/babelorca/mojom/tachyon_parsing_service.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/url_loader_completion_status.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace ash::babelorca {
+namespace {
+
+using RequestDataPtr = std::unique_ptr<RequestDataWrapper>;
+
+constexpr char kOAuthToken[] = "oauth-token";
+constexpr char kUrl[] = "https://test.com";
+
+class FakeTachonParsingService : public mojom::TachyonParsingService {
+ public:
+  explicit FakeTachonParsingService(
+      mojo::PendingReceiver<mojom::TachyonParsingService> receiver)
+      : receiver_(this, std::move(receiver)) {}
+
+  FakeTachonParsingService(const FakeTachonParsingService&) = delete;
+  FakeTachonParsingService& operator=(const FakeTachonParsingService) = delete;
+
+  ~FakeTachonParsingService() override = default;
+
+  void RunParseCallback(mojom::ParsingState state,
+                        std::vector<mojom::BabelOrcaMessagePtr> messages,
+                        mojom::StreamStatusPtr stream_status) {
+    WaitForCallback();
+    std::move(callback_).Run(state, std::move(messages),
+                             std::move(stream_status));
+  }
+
+  int parse_calls() { return parse_calls_; }
+
+ private:
+  // mojom::TachyonParsingService:
+  void Parse(const std::string& stream_data, ParseCallback callback) override {
+    ++parse_calls_;
+    callback_ = std::move(callback);
+    if (run_loop_) {
+      run_loop_->Quit();
+    }
+  }
+
+  void WaitForCallback() {
+    if (callback_) {
+      return;
+    }
+    run_loop_ = std::make_unique<base::RunLoop>();
+    run_loop_->Run();
+  }
+
+  mojo::Receiver<mojom::TachyonParsingService> receiver_;
+
+  ParseCallback callback_;
+
+  std::unique_ptr<base::RunLoop> run_loop_;
+
+  int parse_calls_ = 0;
+};
+
+class TachyonStreamingClientTest : public testing::Test {
+ protected:
+  RequestDataPtr request_data() {
+    auto request_data = std::make_unique<RequestDataWrapper>(
+        TRAFFIC_ANNOTATION_FOR_TESTS, kUrl, /*max_retries_param=*/1,
+        result_future_.GetCallback());
+    request_data->content_data = "request-body";
+    return request_data;
+  }
+
+  mojom::BabelOrcaMessagePtr babel_orca_message_mojom() {
+    mojom::BabelOrcaMessagePtr message = mojom::BabelOrcaMessage::New();
+    message->session_id = "session id";
+    message->init_timestamp_ms = 1234566789;
+    message->order = 3;
+    message->current_transcript = mojom::TranscriptPart::New();
+    message->current_transcript->transcript_id = 12;
+    message->current_transcript->text_index = 5;
+    message->current_transcript->text = "some text";
+    message->current_transcript->is_final = false;
+    message->current_transcript->language = "en";
+    return message;
+  }
+
+  void CreateStreamingClient() {
+    client_ = std::make_unique<TachyonStreamingClient>(
+        url_loader_factory_.GetSafeWeakWrapper(),
+        base::BindLambdaForTesting([this]() {
+          mojo::Remote<mojom::TachyonParsingService> remote;
+          parsing_service_ = std::make_unique<FakeTachonParsingService>(
+              remote.BindNewPipeAndPassReceiver());
+          return remote;
+        }),
+        on_message_future_.GetCallback());
+  }
+
+  std::unique_ptr<TachyonStreamingClient> client_;
+  std::unique_ptr<FakeTachonParsingService> parsing_service_;
+  base::test::TestFuture<void> resume_future_;
+  network::TestURLLoaderFactory url_loader_factory_;
+  base::test::TestFuture<RequestDataPtr> auth_failure_future_;
+  base::test::TestFuture<TachyonResponse> result_future_;
+  base::test::RepeatingTestFuture<mojom::BabelOrcaMessagePtr>
+      on_message_future_;
+  base::test::TaskEnvironment task_env_;
+};
+
+TEST_F(TachyonStreamingClientTest, SuccessfulRequestNoDataStreamed) {
+  url_loader_factory_.AddResponse(kUrl, "");
+
+  CreateStreamingClient();
+  client_->StartRequest(request_data(), kOAuthToken,
+                        auth_failure_future_.GetCallback());
+
+  TachyonResponse result = result_future_.Take();
+  EXPECT_TRUE(result.ok());
+  EXPECT_TRUE(on_message_future_.IsEmpty());
+  EXPECT_FALSE(auth_failure_future_.IsReady());
+}
+
+TEST_F(TachyonStreamingClientTest, HttpErrorNoDataStreamed) {
+  url_loader_factory_.AddResponse(
+      kUrl, "error", net::HttpStatusCode::HTTP_PRECONDITION_FAILED);
+
+  CreateStreamingClient();
+  client_->StartRequest(request_data(), kOAuthToken,
+                        auth_failure_future_.GetCallback());
+
+  TachyonResponse result = result_future_.Take();
+  EXPECT_EQ(result.status(), TachyonResponse::Status::kHttpError);
+  EXPECT_TRUE(on_message_future_.IsEmpty());
+  EXPECT_FALSE(auth_failure_future_.IsReady());
+}
+
+TEST_F(TachyonStreamingClientTest, AuthErrorNoDataStreamed) {
+  url_loader_factory_.AddResponse(kUrl, "error",
+                                  net::HttpStatusCode::HTTP_UNAUTHORIZED);
+
+  CreateStreamingClient();
+  client_->StartRequest(request_data(), kOAuthToken,
+                        auth_failure_future_.GetCallback());
+
+  RequestDataPtr auth_request_data = auth_failure_future_.Take();
+  EXPECT_FALSE(auth_request_data->response_cb.is_null());
+  EXPECT_TRUE(on_message_future_.IsEmpty());
+}
+
+TEST_F(TachyonStreamingClientTest, DataStreamedSuccess) {
+  CreateStreamingClient();
+  client_->StartRequest(request_data(), kOAuthToken,
+                        auth_failure_future_.GetCallback());
+  client_->OnDataReceived("123", resume_future_.GetCallback());
+  std::vector<mojom::BabelOrcaMessagePtr> messages;
+  messages.emplace_back(babel_orca_message_mojom());
+  messages.emplace_back(babel_orca_message_mojom());
+  parsing_service_->RunParseCallback(mojom::ParsingState::kOk,
+                                     std::move(messages), nullptr);
+
+  EXPECT_TRUE(resume_future_.Wait());
+  EXPECT_FALSE(on_message_future_.IsEmpty());
+  on_message_future_.Take();
+  EXPECT_FALSE(on_message_future_.IsEmpty());
+  on_message_future_.Take();
+  EXPECT_TRUE(on_message_future_.IsEmpty());
+  EXPECT_FALSE(result_future_.IsReady());
+  EXPECT_FALSE(auth_failure_future_.IsReady());
+}
+
+TEST_F(TachyonStreamingClientTest, DataStreamedSuccessClosed) {
+  CreateStreamingClient();
+  client_->StartRequest(request_data(), kOAuthToken,
+                        auth_failure_future_.GetCallback());
+  client_->OnDataReceived("123", resume_future_.GetCallback());
+  std::vector<mojom::BabelOrcaMessagePtr> messages;
+  messages.emplace_back(babel_orca_message_mojom());
+  mojom::StreamStatusPtr stream_status =
+      mojom::StreamStatus::New(/*code=*/0, /*message=*/"success");
+  parsing_service_->RunParseCallback(mojom::ParsingState::kClosed,
+                                     std::move(messages),
+                                     std::move(stream_status));
+
+  TachyonResponse result = result_future_.Take();
+  EXPECT_TRUE(result.ok());
+  EXPECT_FALSE(auth_failure_future_.IsReady());
+  EXPECT_FALSE(resume_future_.IsReady());
+  EXPECT_FALSE(on_message_future_.IsEmpty());
+  on_message_future_.Take();
+  EXPECT_TRUE(on_message_future_.IsEmpty());
+}
+
+TEST_F(TachyonStreamingClientTest, DataStreamedParsingError) {
+  CreateStreamingClient();
+  client_->StartRequest(request_data(), kOAuthToken,
+                        auth_failure_future_.GetCallback());
+  client_->OnDataReceived("123", resume_future_.GetCallback());
+  std::vector<mojom::BabelOrcaMessagePtr> messages;
+  messages.emplace_back(babel_orca_message_mojom());
+  mojom::StreamStatusPtr stream_status =
+      mojom::StreamStatus::New(/*code=*/0, /*message=*/"success");
+  parsing_service_->RunParseCallback(mojom::ParsingState::kError,
+                                     std::move(messages),
+                                     std::move(stream_status));
+
+  TachyonResponse result = result_future_.Take();
+  EXPECT_EQ(result.status(), TachyonResponse::Status::kInternalError);
+  EXPECT_FALSE(auth_failure_future_.IsReady());
+  EXPECT_FALSE(resume_future_.IsReady());
+  EXPECT_FALSE(on_message_future_.IsEmpty());
+  on_message_future_.Take();
+  EXPECT_TRUE(on_message_future_.IsEmpty());
+}
+
+TEST_F(TachyonStreamingClientTest, DataStreamedAuthErrorClosed) {
+  CreateStreamingClient();
+  client_->StartRequest(request_data(), kOAuthToken,
+                        auth_failure_future_.GetCallback());
+  client_->OnDataReceived("123", resume_future_.GetCallback());
+  mojom::StreamStatusPtr stream_status =
+      mojom::StreamStatus::New(/*code=*/16, /*message=*/"auth error");
+  parsing_service_->RunParseCallback(mojom::ParsingState::kClosed, {},
+                                     std::move(stream_status));
+
+  EXPECT_TRUE(auth_failure_future_.Wait());
+  EXPECT_FALSE(result_future_.IsReady());
+  EXPECT_FALSE(resume_future_.IsReady());
+  EXPECT_TRUE(on_message_future_.IsEmpty());
+}
+
+TEST_F(TachyonStreamingClientTest, ParsingServiceDisconnected) {
+  CreateStreamingClient();
+  client_->StartRequest(request_data(), kOAuthToken,
+                        auth_failure_future_.GetCallback());
+  client_->OnDataReceived("123", resume_future_.GetCallback());
+  parsing_service_->RunParseCallback(mojom::ParsingState::kOk, {}, nullptr);
+  EXPECT_TRUE(resume_future_.Wait());
+  // Destroy parsing service.
+  parsing_service_.reset();
+
+  TachyonResponse result = result_future_.Take();
+  EXPECT_EQ(result.status(), TachyonResponse::Status::kInternalError);
+}
+
+TEST_F(TachyonStreamingClientTest, UseSingleParsingServicePerConnection) {
+  CreateStreamingClient();
+  client_->StartRequest(request_data(), kOAuthToken,
+                        auth_failure_future_.GetCallback());
+  client_->OnDataReceived("123", resume_future_.GetCallback());
+  parsing_service_->RunParseCallback(mojom::ParsingState::kOk, {}, nullptr);
+  EXPECT_TRUE(resume_future_.Wait());
+
+  resume_future_.Clear();
+  client_->OnDataReceived("456", resume_future_.GetCallback());
+  parsing_service_->RunParseCallback(mojom::ParsingState::kOk, {}, nullptr);
+
+  EXPECT_TRUE(resume_future_.Wait());
+  EXPECT_EQ(parsing_service_->parse_calls(), 2);
+}
+
+TEST_F(TachyonStreamingClientTest, ResetParsingServiceOnRetry) {
+  base::test::TestFuture<void> retry_future;
+
+  CreateStreamingClient();
+  client_->StartRequest(request_data(), kOAuthToken,
+                        auth_failure_future_.GetCallback());
+  client_->OnDataReceived("123", resume_future_.GetCallback());
+  parsing_service_->RunParseCallback(mojom::ParsingState::kOk, {}, nullptr);
+  EXPECT_TRUE(resume_future_.Wait());
+  EXPECT_EQ(parsing_service_->parse_calls(), 1);
+
+  resume_future_.Clear();
+  client_->OnRetry(retry_future.GetCallback());
+  client_->OnDataReceived("456", resume_future_.GetCallback());
+  parsing_service_->RunParseCallback(mojom::ParsingState::kOk, {}, nullptr);
+
+  EXPECT_TRUE(retry_future.IsReady());
+  EXPECT_TRUE(resume_future_.Wait());
+  EXPECT_EQ(parsing_service_->parse_calls(), 1);
+}
+
+}  // namespace
+}  // namespace ash::babelorca
diff --git a/chromeos/ash/components/boca/session_api/get_session_request.cc b/chromeos/ash/components/boca/session_api/get_session_request.cc
index 260c2ae..a0a93292 100644
--- a/chromeos/ash/components/boca/session_api/get_session_request.cc
+++ b/chromeos/ash/components/boca/session_api/get_session_request.cc
@@ -36,12 +36,12 @@
 namespace ash::boca {
 
 GetSessionRequest::GetSessionRequest(google_apis::RequestSender* sender,
-                                     const std::string gaia_id,
+                                     std::string gaia_id,
                                      Callback callback)
     : UrlFetchRequestBase(sender,
                           google_apis::ProgressCallback(),
                           google_apis::ProgressCallback()),
-      gaia_id_(gaia_id),
+      gaia_id_(std::move(gaia_id)),
       url_base_(kSchoolToolsApiBaseUrl),
       callback_(std::move(callback)) {}
 
diff --git a/chromeos/ash/components/cryptohome/auth_factor_input.cc b/chromeos/ash/components/cryptohome/auth_factor_input.cc
index 3fad01c..7ba5dfc2 100644
--- a/chromeos/ash/components/cryptohome/auth_factor_input.cc
+++ b/chromeos/ash/components/cryptohome/auth_factor_input.cc
@@ -31,10 +31,10 @@
 
 // =============== `SmartCard` =====================
 AuthFactorInput::SmartCard::SmartCard(
-    const std::vector<ChallengeResponseKey::SignatureAlgorithm> algorithms,
-    const std::string dbus_service_name)
-    : signature_algorithms(algorithms),
-      key_delegate_dbus_service_name(dbus_service_name) {}
+    std::vector<ChallengeResponseKey::SignatureAlgorithm> algorithms,
+    std::string dbus_service_name)
+    : signature_algorithms(std::move(algorithms)),
+      key_delegate_dbus_service_name(std::move(dbus_service_name)) {}
 AuthFactorInput::SmartCard::SmartCard(const SmartCard& other) = default;
 AuthFactorInput::SmartCard& AuthFactorInput::SmartCard::operator=(
     const SmartCard&) = default;
diff --git a/chromeos/ash/components/cryptohome/auth_factor_input.h b/chromeos/ash/components/cryptohome/auth_factor_input.h
index 4709a58..245fcf9 100644
--- a/chromeos/ash/components/cryptohome/auth_factor_input.h
+++ b/chromeos/ash/components/cryptohome/auth_factor_input.h
@@ -47,9 +47,9 @@
   };
 
   struct SmartCard {
-    SmartCard(const std::vector<ChallengeResponseKey::SignatureAlgorithm>
+    SmartCard(std::vector<ChallengeResponseKey::SignatureAlgorithm>
                   signature_algorithms,
-              const std::string key_delegate_dbus_service_name);
+              std::string key_delegate_dbus_service_name);
     SmartCard(const SmartCard& other);
     SmartCard& operator=(const SmartCard&);
     ~SmartCard();
diff --git a/chromeos/ash/components/dbus/cros_disks/cros_disks_client.cc b/chromeos/ash/components/dbus/cros_disks/cros_disks_client.cc
index 988fc6b..9f4368c 100644
--- a/chromeos/ash/components/dbus/cros_disks/cros_disks_client.cc
+++ b/chromeos/ash/components/dbus/cros_disks/cros_disks_client.cc
@@ -877,7 +877,7 @@
 // static
 std::vector<std::string> CrosDisksClient::ComposeMountOptions(
     std::vector<std::string> options,
-    const std::string_view mount_label,
+    std::string_view mount_label,
     const MountAccessMode access_mode,
     const RemountOption remount) {
   options.push_back(access_mode == MountAccessMode::kReadWrite ? "rw" : "ro");
diff --git a/chromeos/ash/components/dbus/shill/shill_manager_client.cc b/chromeos/ash/components/dbus/shill/shill_manager_client.cc
index fd03d5c..1a9ee764 100644
--- a/chromeos/ash/components/dbus/shill/shill_manager_client.cc
+++ b/chromeos/ash/components/dbus/shill/shill_manager_client.cc
@@ -44,12 +44,12 @@
     default;
 
 ShillManagerClient::ConnectP2PGroupParameter::ConnectP2PGroupParameter(
-    const std::string ssid,
-    const std::string passphrase,
+    std::string ssid,
+    std::string passphrase,
     const std::optional<uint32_t> frequency,
     const std::optional<shill::WiFiInterfacePriority> priority)
-    : ssid(ssid),
-      passphrase(passphrase),
+    : ssid(std::move(ssid)),
+      passphrase(std::move(passphrase)),
       frequency(frequency),
       priority(priority) {}
 
diff --git a/components/autofill/core/browser/form_data_importer.cc b/components/autofill/core/browser/form_data_importer.cc
index 66bce20..013476e 100644
--- a/components/autofill/core/browser/form_data_importer.cc
+++ b/components/autofill/core/browser/form_data_importer.cc
@@ -118,12 +118,6 @@
 bool ShouldOfferVirtualCardEnrollment(
     const std::optional<CreditCard>& extracted_credit_card,
     std::optional<int64_t> fetched_card_instrument_id) {
-#if BUILDFLAG(IS_IOS)
-  if (!base::FeatureList::IsEnabled(features::kAutofillEnableVirtualCards)) {
-    return false;
-  }
-#endif
-
   if (!extracted_credit_card) {
     return false;
   }
diff --git a/components/autofill/core/browser/payments/autofill_payments_feature_availability.cc b/components/autofill/core/browser/payments/autofill_payments_feature_availability.cc
index e492e4d..13fcf0d 100644
--- a/components/autofill/core/browser/payments/autofill_payments_feature_availability.cc
+++ b/components/autofill/core/browser/payments/autofill_payments_feature_availability.cc
@@ -33,14 +33,6 @@
               .empty();
 }
 
-bool VirtualCardFeatureEnabled() {
-#if BUILDFLAG(IS_IOS)
-  return base::FeatureList::IsEnabled(features::kAutofillEnableVirtualCards);
-#else
-  return true;
-#endif
-}
-
 bool IsVcn3dsEnabled() {
   return base::FeatureList::IsEnabled(
              features::kAutofillEnableVcn3dsAuthentication) &&
diff --git a/components/autofill/core/browser/payments/autofill_payments_feature_availability.h b/components/autofill/core/browser/payments/autofill_payments_feature_availability.h
index a6ad703a..a80cf9d3 100644
--- a/components/autofill/core/browser/payments/autofill_payments_feature_availability.h
+++ b/components/autofill/core/browser/payments/autofill_payments_feature_availability.h
@@ -21,9 +21,6 @@
 // product name and whether they both should be shown.
 bool ShouldShowCardMetadata(const CreditCard& card);
 
-// Returns whether the virtual card is supported.
-bool VirtualCardFeatureEnabled();
-
 // Returns whether VCN 3DS authentication is enabled and can be used as an
 // authentication option.
 bool IsVcn3dsEnabled();
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.cc b/components/autofill/core/browser/payments/credit_card_access_manager.cc
index e5c4705..95d550a 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.cc
@@ -424,25 +424,11 @@
   unmasked_card_cache_[identifier] = card_info;
 }
 
-bool AreVirtualCardsSupported() {
-#if BUILDFLAG(IS_IOS)
-  return base::FeatureList::IsEnabled(features::kAutofillEnableVirtualCards);
-#else
-  return true;
-#endif
-}
-
 void CreditCardAccessManager::StartAuthenticationFlow(bool fido_auth_enabled) {
-  if (AreVirtualCardsSupported()) {
-    if (card_->record_type() == CreditCard::RecordType::kVirtualCard) {
-      StartAuthenticationFlowForVirtualCard(fido_auth_enabled);
-    } else {
-      StartAuthenticationFlowForMaskedServerCard(fido_auth_enabled);
-    }
+  if (card_->record_type() == CreditCard::RecordType::kVirtualCard) {
+    StartAuthenticationFlowForVirtualCard(fido_auth_enabled);
   } else {
-    // There is no FIDO auth available on iOS and until the feature is live
-    // there are no virtual cards, so offer CVC auth.
-    Authenticate(UnmaskAuthFlowType::kCvc);
+    StartAuthenticationFlowForMaskedServerCard(fido_auth_enabled);
   }
 }
 
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
index db2e81ab..d9cb143 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
@@ -2070,78 +2070,9 @@
 }
 
 TEST_F(CreditCardAccessManagerTest,
-       RiskBasedVirtualCardUnmasking_Success_VirtualCardsEnabled) {
+       RiskBasedVirtualCardUnmasking_Success_VirtualCards) {
   base::HistogramTester histogram_tester;
 
-#if BUILDFLAG(IS_IOS)
-  base::test::ScopedFeatureList scoped_feature_list{
-      features::kAutofillEnableVirtualCards};
-#endif  // BUILDFLAG(IS_IOS)
-
-#if BUILDFLAG(IS_ANDROID)
-  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
-    GTEST_SKIP() << "This test should not run on automotive.";
-  }
-#endif  // BUILDFLAG(IS_ANDROID)
-
-  const CreditCard* masked_server_card =
-      CreateServerCard(kTestGUID, kTestNumber, kTestServerId);
-  CreditCard virtual_card = *masked_server_card;
-  virtual_card.set_record_type(CreditCard::RecordType::kVirtualCard);
-
-  credit_card_access_manager().FetchCreditCard(
-      &virtual_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
-                                    accessor_->GetWeakPtr()));
-
-  // This checks risk-based authentication flow is successfully invoked,
-  // because it is always the very first authentication flow in a VCN
-  // unmasking flow.
-  EXPECT_TRUE(autofill_client_.GetPaymentsAutofillClient()
-                  ->risk_based_authentication_invoked());
-  // Mock server response with valid card information.
-  payments::PaymentsNetworkInterface::UnmaskResponseDetails response;
-  response.real_pan = "4111111111111111";
-  response.dcvv = "321";
-  response.expiration_month = test::NextMonth();
-  response.expiration_year = test::NextYear();
-  response.card_type = PaymentsRpcCardType::kVirtualCard;
-  credit_card_access_manager()
-      .OnVirtualCardRiskBasedAuthenticationResponseReceived(
-          PaymentsRpcResult::kSuccess, response);
-
-  // Ensure the accessor received the correct response.
-  EXPECT_EQ(accessor_->result(), CreditCardFetchResult::kSuccess);
-  EXPECT_EQ(accessor_->number(), u"4111111111111111");
-  EXPECT_EQ(accessor_->cvc(), u"321");
-  EXPECT_EQ(accessor_->expiry_month(), base::UTF8ToUTF16(test::NextMonth()));
-  EXPECT_EQ(accessor_->expiry_year(), base::UTF8ToUTF16(test::NextYear()));
-
-  // There was no interactive authentication in this flow, so check that this
-  // is signaled correctly.
-  std::optional<NonInteractivePaymentMethodType> type =
-      test_api(*autofill_client_.GetFormDataImporter())
-          .payment_method_type_if_non_interactive_authentication_flow_completed();
-  EXPECT_THAT(type,
-              testing::Optional(NonInteractivePaymentMethodType::kVirtualCard));
-
-  // Expect the metrics are logged correctly.
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.ServerCardUnmask.VirtualCard.Attempt", true, 1);
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.ServerCardUnmask.VirtualCard.Result.RiskBased",
-      autofill_metrics::ServerCardUnmaskResult::kRiskBasedUnmasked, 1);
-}
-
-TEST_F(CreditCardAccessManagerTest,
-       RiskBasedVirtualCardUnmasking_Success_VirtualCardsDisabled) {
-  base::HistogramTester histogram_tester;
-
-#if BUILDFLAG(IS_IOS)
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(
-      features::kAutofillEnableVirtualCards);
-#endif  // BUILDFLAG(IS_IOS)
-
 #if BUILDFLAG(IS_ANDROID)
   if (base::android::BuildInfo::GetInstance()->is_automotive()) {
     GTEST_SKIP() << "This test should not run on automotive.";
@@ -2203,11 +2134,6 @@
        RiskBasedVirtualCardUnmasking_AuthenticationRequired_OtpOnly) {
   base::HistogramTester histogram_tester;
 
-#if BUILDFLAG(IS_IOS)
-  base::test::ScopedFeatureList scoped_feature_list{
-      features::kAutofillEnableVirtualCards};
-#endif  // BUILDFLAG(IS_IOS)
-
   std::vector<CardUnmaskChallengeOption> challenge_options =
       test::GetCardUnmaskChallengeOptions(
           {CardUnmaskChallengeOptionType::kSmsOtp,
@@ -2248,11 +2174,6 @@
        RiskBasedVirtualCardUnmasking_AuthenticationRequired_CvcOnly) {
   base::HistogramTester histogram_tester;
 
-#if BUILDFLAG(IS_IOS)
-  base::test::ScopedFeatureList scoped_feature_list{
-      features::kAutofillEnableVirtualCards};
-#endif  // BUILDFLAG(IS_IOS)
-
   std::vector<CardUnmaskChallengeOption> challenge_options =
       test::GetCardUnmaskChallengeOptions(
           {CardUnmaskChallengeOptionType::kCvc});
@@ -2284,11 +2205,6 @@
 // kThreeDomainSecure when only the 3DS challenge option is returned.
 TEST_F(CreditCardAccessManagerTest,
        RiskBasedVirtualCardUnmasking_Only3dsChallengeReturned) {
-#if BUILDFLAG(IS_IOS)
-  base::test::ScopedFeatureList scoped_feature_list{
-      features::kAutofillEnableVirtualCards};
-#endif  // BUILDFLAG(IS_IOS)
-
   base::HistogramTester histogram_tester;
   const CreditCard* masked_server_card =
       CreateServerCard(kTestGUID, kTestNumber, kTestServerId);
@@ -2335,11 +2251,6 @@
        RiskBasedVirtualCardUnmasking_AuthenticationRequired_OtpAndCvcAnd3ds) {
   base::HistogramTester histogram_tester;
 
-#if BUILDFLAG(IS_IOS)
-  base::test::ScopedFeatureList scoped_feature_list{
-      features::kAutofillEnableVirtualCards};
-#endif  // BUILDFLAG(IS_IOS)
-
   std::vector<CardUnmaskChallengeOption> challenge_options =
       test::GetCardUnmaskChallengeOptions(
           {CardUnmaskChallengeOptionType::kSmsOtp,
diff --git a/components/autofill/core/browser/payments/credit_card_save_manager.cc b/components/autofill/core/browser/payments/credit_card_save_manager.cc
index 16b58a4..adf783b 100644
--- a/components/autofill/core/browser/payments/credit_card_save_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_save_manager.cc
@@ -106,8 +106,7 @@
   // them here and start the flow.
   if (card_saved &&
       upload_card_response_details.virtual_card_enrollment_state ==
-          CreditCard::VirtualCardEnrollmentState::kUnenrolledAndEligible &&
-      VirtualCardFeatureEnabled()) {
+          CreditCard::VirtualCardEnrollmentState::kUnenrolledAndEligible) {
     DCHECK(upload_card_response_details.instrument_id.has_value());
     if (!upload_card_response_details.card_art_url.is_empty()) {
       uploaded_card->set_card_art_url(
@@ -1253,11 +1252,9 @@
         user_provided_card_details.expiration_date_year, app_locale_);
   }
 
-  if (VirtualCardFeatureEnabled()) {
-    client_->GetPaymentsAutofillClient()
-        ->GetVirtualCardEnrollmentManager()
-        ->SetSaveCardBubbleAcceptedTimestamp(AutofillClock::Now());
-  }
+  client_->GetPaymentsAutofillClient()
+      ->GetVirtualCardEnrollmentManager()
+      ->SetSaveCardBubbleAcceptedTimestamp(AutofillClock::Now());
 
   // Populating risk data and offering upload occur asynchronously.
   // If |risk_data| has already been loaded, send the upload card request.
diff --git a/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc b/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
index 4a28d850a..502e36d 100644
--- a/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
@@ -6084,14 +6084,9 @@
 TEST_P(CreditCardSaveManagerWithLoadingAndConfirmation,
        InitVirtualCardEnroll_LoadingAndConfirmation) {
   base::test::ScopedFeatureList feature_list;
-  base::flat_map<base::test::FeatureRef, bool> feature_states;
-#if BUILDFLAG(IS_IOS)
-  feature_states.insert({features::kAutofillEnableVirtualCards, true});
-#endif
-  feature_states.insert(
-      {features::kAutofillEnableSaveCardLoadingAndConfirmation,
-       IsSaveCardLoadingAndConfirmationEnabled()});
-  feature_list.InitWithFeatureStates(feature_states);
+  feature_list.InitWithFeatureState(
+      features::kAutofillEnableSaveCardLoadingAndConfirmation,
+      IsSaveCardLoadingAndConfirmationEnabled());
 
   payments::PaymentsNetworkInterface::UploadCardResponseDetails
       upload_card_response_details;
@@ -6135,37 +6130,21 @@
 class CreditCardSaveManagerWithVirtualCardEnrollTestParameterized
     : public CreditCardSaveManagerTest,
       public testing::WithParamInterface<
-          std::tuple<CreditCard::VirtualCardEnrollmentState, bool>> {
+          std::tuple<CreditCard::VirtualCardEnrollmentState>> {
  public:
   CreditCardSaveManagerWithVirtualCardEnrollTestParameterized() = default;
 
   CreditCard::VirtualCardEnrollmentState GetEnrollmentState() const {
     return std::get<0>(GetParam());
   }
-
-  bool IsVirtualCardEnrollmentEnabled() const {
-    return std::get<1>(GetParam());
-  }
 };
 
 // Tests that the fields in the card to be enrolled as virtual card are set
 // correctly only when a card becomes eligible after upload.
 TEST_P(CreditCardSaveManagerWithVirtualCardEnrollTestParameterized,
        PrepareUploadedCardForVirtualCardEnrollment) {
-  base::test::ScopedFeatureList feature_list;
-  base::flat_map<base::test::FeatureRef, bool> feature_states;
-#if !BUILDFLAG(IS_IOS)
-  if (!IsVirtualCardEnrollmentEnabled()) {
-    GTEST_SKIP() << "Virtual card enrollment is always enabled on non-iOS "
-                    "platforms.";
-  }
-#else
-  feature_states.insert({features::kAutofillEnableVirtualCards,
-                         IsVirtualCardEnrollmentEnabled()});
-#endif
-  feature_states.insert(
-      {features::kAutofillEnableSaveCardLoadingAndConfirmation, true});
-  feature_list.InitWithFeatureStates(feature_states);
+  base::test::ScopedFeatureList feature_list{
+      features::kAutofillEnableSaveCardLoadingAndConfirmation};
   payments::PaymentsNetworkInterface::UploadCardResponseDetails
       upload_card_response_details;
   upload_card_response_details.card_art_url = GURL("https://www.example.com/");
@@ -6196,9 +6175,8 @@
                   A<std::optional<payments::PaymentsAutofillClient::
                                       OnConfirmationClosedCallback>>()));
 
-  if (IsVirtualCardEnrollmentEnabled() &&
-      GetEnrollmentState() ==
-          CreditCard::VirtualCardEnrollmentState::kUnenrolledAndEligible) {
+  if (GetEnrollmentState() ==
+      CreditCard::VirtualCardEnrollmentState::kUnenrolledAndEligible) {
     EXPECT_CALL(*static_cast<MockVirtualCardEnrollmentManager*>(
                     payments_client().GetVirtualCardEnrollmentManager()),
                 InitVirtualCardEnroll)
@@ -6223,9 +6201,8 @@
 
   // The condition inside of this if-statement is true if virtual card
   // enrollment should be offered.
-  if (IsVirtualCardEnrollmentEnabled() &&
-      GetEnrollmentState() ==
-          CreditCard::VirtualCardEnrollmentState::kUnenrolledAndEligible) {
+  if (GetEnrollmentState() ==
+      CreditCard::VirtualCardEnrollmentState::kUnenrolledAndEligible) {
     EXPECT_EQ(arg_credit_card.card_art_url(),
               upload_card_response_details.card_art_url);
     EXPECT_EQ(arg_credit_card.instrument_id(),
@@ -6265,7 +6242,6 @@
         /*enrollment_state*/ testing::Values(
             CreditCard::VirtualCardEnrollmentState::kUnenrolled,
             CreditCard::VirtualCardEnrollmentState::kUnenrolledAndEligible,
-            CreditCard::VirtualCardEnrollmentState::kEnrolled),
-        /*is_virtual_card_enrollment_enabled*/ testing::Bool()));
+            CreditCard::VirtualCardEnrollmentState::kEnrolled)));
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc
index 6c2bba43..2c5b6e80 100644
--- a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc
+++ b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc
@@ -59,8 +59,7 @@
       payments_network_interface_(payments_network_interface) {
   // |autofill_client_| does not exist on Clank settings page where this flow
   // can also be triggered.
-  if (VirtualCardFeatureEnabled() && autofill_client_ &&
-      autofill_client_->GetStrikeDatabase()) {
+  if (autofill_client_ && autofill_client_->GetStrikeDatabase()) {
     virtual_card_enrollment_strike_database_ =
         std::make_unique<VirtualCardEnrollmentStrikeDatabase>(
             autofill_client_->GetStrikeDatabase());
@@ -80,8 +79,7 @@
     VirtualCardEnrollmentFieldsLoadedCallback
         virtual_card_enrollment_fields_loaded_callback) {
   // If at strike limit, exit enrollment flow.
-  if (VirtualCardFeatureEnabled() &&
-      ShouldBlockVirtualCardEnrollment(
+  if (ShouldBlockVirtualCardEnrollment(
           base::NumberToString(credit_card.instrument_id()),
           virtual_card_enrollment_source)) {
     Reset();
@@ -143,10 +141,8 @@
                          OnDidGetUpdateVirtualCardEnrollmentResponse,
                      weak_ptr_factory_.GetWeakPtr(),
                      VirtualCardEnrollmentRequestType::kEnroll));
-  if (VirtualCardFeatureEnabled()) {
     RemoveAllStrikesToBlockOfferingVirtualCardEnrollment(base::NumberToString(
         state_.virtual_card_enrollment_fields.credit_card.instrument_id()));
-  }
 }
 
 void VirtualCardEnrollmentManager::Unenroll(
@@ -359,10 +355,8 @@
 }
 
 void VirtualCardEnrollmentManager::OnVirtualCardEnrollmentBubbleCancelled() {
-  if (VirtualCardFeatureEnabled()) {
     AddStrikeToBlockOfferingVirtualCardEnrollment(base::NumberToString(
         state_.virtual_card_enrollment_fields.credit_card.instrument_id()));
-  }
   Reset();
 }
 
diff --git a/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc b/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc
index 3b041500..39b8a0b 100644
--- a/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc
+++ b/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc
@@ -41,16 +41,6 @@
           : IDS_AUTOFILL_CARD_UNMASK_PROMPT_SECURITY_CODE_POSITION_BACK_OF_CARD);
 }
 
-#if BUILDFLAG(IS_IOS)
-// Returns whether the virtual card feature is enabled on IOS. If true, a UI
-// overhaul which was proposed as part of the virtual card feature launch, will
-// be enabled regardless of whether the card being unmasked is a virtual card or
-// not.
-bool VirtualCardFeatureEnabled() {
-  return base::FeatureList::IsEnabled(features::kAutofillEnableVirtualCards);
-}
-#endif
-
 }  // namespace
 
 CardUnmaskPromptControllerImpl::CardUnmaskPromptControllerImpl(
@@ -208,22 +198,11 @@
 #if BUILDFLAG(IS_IOS)
 std::u16string CardUnmaskPromptControllerImpl::GetNavigationTitle() const {
   return l10n_util::GetStringUTF16(
-      VirtualCardFeatureEnabled()
-          ? IDS_AUTOFILL_CARD_UNMASK_PROMPT_NAVIGATION_TITLE_VERIFICATION
-          : IDS_AUTOFILL_CARD_UNMASK_PROMPT_NAVIGATION_TITLE);
+      IDS_AUTOFILL_CARD_UNMASK_PROMPT_NAVIGATION_TITLE_VERIFICATION);
 }
 #endif
 
 std::u16string CardUnmaskPromptControllerImpl::GetWindowTitle() const {
-#if BUILDFLAG(IS_IOS)
-  // - For IOS, if the virtual card feature is not enabled, don't show any
-  //   title. Use the instruction message below instead.
-  // - If it is enabled, share the same string below with other platforms.
-  if (!VirtualCardFeatureEnabled()) {
-    return std::u16string();
-  }
-#endif
-
   // Set title for VCN retrieval errors first.
   if (unmasking_result_ == PaymentsRpcResult::kVcnRetrievalPermanentFailure) {
     return l10n_util::GetStringUTF16(
@@ -269,22 +248,6 @@
 }
 
 std::u16string CardUnmaskPromptControllerImpl::GetInstructionsMessage() const {
-#if BUILDFLAG(IS_IOS)
-  if (!VirtualCardFeatureEnabled()) {
-    int ids;
-    if (card_unmask_prompt_options_.reason ==
-            payments::PaymentsAutofillClient::UnmaskCardReason::kAutofill &&
-        ShouldRequestExpirationDate()) {
-      ids = IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_EXPIRED;
-    } else {
-      ids = IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS;
-    }
-    // The iOS UI shows the card details in the instructions text since they
-    // don't fit in the title.
-    return l10n_util::GetStringFUTF16(ids, card_.CardNameAndLastFourDigits());
-  }
-#endif
-
   // If the challenge option is present, return the challenge option instruction
   // information.
   if (IsChallengeOptionPresent()) {
diff --git a/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl_unittest.cc b/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl_unittest.cc
index 3a64f53..34efe5b 100644
--- a/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl_unittest.cc
+++ b/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl_unittest.cc
@@ -311,37 +311,15 @@
 
 // Tests to ensure the UI text elements are shown correctly in the card unmask
 // prompt.
-// The parameter indicates whether the virtual card feature is enabled on Bling.
-// It decides the text content we show in the promp.
 class CardUnmaskPromptTextTest
-    : public CardUnmaskPromptControllerImplGenericTest,
-      public testing::WithParamInterface<bool> {
- public:
-  void SetUp() override {
-    CardUnmaskPromptControllerImplGenericTest::SetUp();
-#if BUILDFLAG(IS_IOS)
-    scoped_feature_list_.InitWithFeatureState(
-        features::kAutofillEnableVirtualCards, GetParam());
-#endif
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-INSTANTIATE_TEST_SUITE_P(CardUnmaskPromptControllerImplGenericTest,
-                         CardUnmaskPromptTextTest,
-                         ::testing::Bool());
+    : public CardUnmaskPromptControllerImplGenericTest {};
 
 // Ensures the card information is shown correctly.
-TEST_P(CardUnmaskPromptTextTest, DisplayCardInformation) {
+TEST_F(CardUnmaskPromptTextTest, DisplayCardInformation) {
   ShowPrompt();
 #if BUILDFLAG(IS_IOS)
-  // On IOS, if feature is enabled, we don't show card information in the
-  // instructions.
-  EXPECT_EQ(GetParam(),
-            controller_->GetInstructionsMessage().find(
-                card_.CardNameAndLastFourDigits()) == std::string::npos);
+  EXPECT_TRUE(controller_->GetInstructionsMessage().find(
+                  card_.CardNameAndLastFourDigits()) == std::string::npos);
 #elif BUILDFLAG(IS_ANDROID)
   EXPECT_EQ(controller_->GetCardName(), card_.CardNameForAutofillDisplay());
   EXPECT_EQ(controller_->GetCardLastFourDigits(),
@@ -353,23 +331,14 @@
 }
 
 // Tests the title and instructions message in the credit card unmask dialog.
-TEST_P(CardUnmaskPromptTextTest, TitleAndInstructionMessage) {
+TEST_F(CardUnmaskPromptTextTest, TitleAndInstructionMessage) {
   ShowPrompt();
 #if BUILDFLAG(IS_IOS)
-  if (GetParam()) {
-    EXPECT_EQ(controller_->GetNavigationTitle(), u"Verification");
-    EXPECT_EQ(controller_->GetWindowTitle(), u"Enter your CVC");
-    EXPECT_EQ(controller_->GetInstructionsMessage(),
-              u"To help keep your card secure, enter the CVC on the back of "
-              u"your card");
-  } else {
-    EXPECT_EQ(controller_->GetNavigationTitle(), u"Confirm Card");
-    EXPECT_EQ(controller_->GetWindowTitle(), u"");
-    EXPECT_EQ(controller_->GetInstructionsMessage(),
-              u"Enter the CVC for " + card_.CardNameAndLastFourDigits() +
-                  u". After you confirm, card details from your Google Account "
-                  u"will be shared with this site.");
-  }
+  EXPECT_EQ(controller_->GetNavigationTitle(), u"Verification");
+  EXPECT_EQ(controller_->GetWindowTitle(), u"Enter your CVC");
+  EXPECT_EQ(controller_->GetInstructionsMessage(),
+            u"To help keep your card secure, enter the CVC on the back of "
+            u"your card");
 #else
 
 #if BUILDFLAG(IS_ANDROID)
@@ -389,26 +358,17 @@
   DismissPrompt();
 }
 
-TEST_P(CardUnmaskPromptTextTest, TitleAndInstructionMessageAmex) {
+TEST_F(CardUnmaskPromptTextTest, TitleAndInstructionMessageAmex) {
   // On Amex cards, the CVC is present on the front of the card. Test that the
   // dialog relays this information to the users.
   card_ = test::GetMaskedServerCardAmex();
   ShowPrompt();
 #if BUILDFLAG(IS_IOS)
-  if (GetParam()) {
-    EXPECT_EQ(controller_->GetNavigationTitle(), u"Verification");
-    EXPECT_EQ(controller_->GetWindowTitle(), u"Enter your CVC");
-    EXPECT_EQ(controller_->GetInstructionsMessage(),
-              u"To help keep your card secure, enter the CVC on the front of "
-              u"your card");
-  } else {
-    EXPECT_EQ(controller_->GetNavigationTitle(), u"Confirm Card");
-    EXPECT_EQ(controller_->GetWindowTitle(), u"");
-    EXPECT_EQ(controller_->GetInstructionsMessage(),
-              u"Enter the CVC for " + card_.CardNameAndLastFourDigits() +
-                  u". After you confirm, card details from your Google Account "
-                  u"will be shared with this site.");
-  }
+  EXPECT_EQ(controller_->GetNavigationTitle(), u"Verification");
+  EXPECT_EQ(controller_->GetWindowTitle(), u"Enter your CVC");
+  EXPECT_EQ(controller_->GetInstructionsMessage(),
+            u"To help keep your card secure, enter the CVC on the front of "
+            u"your card");
 #else
 
 #if BUILDFLAG(IS_ANDROID)
@@ -430,26 +390,14 @@
 
 // Tests the title and instructions message in the credit card unmask dialog for
 // expired cards.
-TEST_P(CardUnmaskPromptTextTest, ExpiredCardTitleAndInstructionMessage) {
+TEST_F(CardUnmaskPromptTextTest, ExpiredCardTitleAndInstructionMessage) {
   card_ = test::GetExpiredCreditCard();
   ShowPrompt();
 #if BUILDFLAG(IS_IOS)
-  if (GetParam()) {
-    EXPECT_EQ(controller_->GetNavigationTitle(), u"Verification");
-    EXPECT_EQ(controller_->GetWindowTitle(), u"Card expired");
-    EXPECT_EQ(
-        controller_->GetInstructionsMessage(),
-        u"Enter your new expiration date and CVC on the back of your card");
-  } else {
-    EXPECT_EQ(controller_->GetNavigationTitle(), u"Confirm Card");
-    EXPECT_EQ(controller_->GetWindowTitle(), u"");
-    EXPECT_EQ(controller_->GetInstructionsMessage(),
-              u"Enter the expiration date and CVC for " +
-                  card_.CardNameAndLastFourDigits() +
-                  u" to update your card details. After you confirm, card "
-                  u"details from "
-                  u"your Google Account will be shared with this site.");
-  }
+  EXPECT_EQ(controller_->GetNavigationTitle(), u"Verification");
+  EXPECT_EQ(controller_->GetWindowTitle(), u"Card expired");
+  EXPECT_EQ(controller_->GetInstructionsMessage(),
+            u"Enter your new expiration date and CVC on the back of your card");
 
 #else
 
@@ -472,13 +420,8 @@
 // showing the card unmask prompt for a virtual card. This test also checks that
 // the expected CVC length is correctly set for virtual cards. Virtual cards are
 // not currently supported on iOS, so we don't test on the platform.
-TEST_P(CardUnmaskPromptTextTest,
+TEST_F(CardUnmaskPromptTextTest,
        ChallengeOptionInstructionMessageAndWindowTitleAndExpectedCvcLength) {
-#if BUILDFLAG(IS_IOS)
-  if (!GetParam()) {
-    GTEST_SKIP() << "This test requires the virtual card feature.";
-  }
-#endif
   // Test that if the network is not American Express and the challenge option
   // denotes that the security code is on the back of the card, its expected
   // length is 3.
@@ -499,14 +442,9 @@
   DismissPrompt();
 }
 
-TEST_P(
+TEST_F(
     CardUnmaskPromptTextTest,
     ChallengeOptionInstructionMessageAndWindowTitleAndExpectedCvcLengthAmex) {
-#if BUILDFLAG(IS_IOS)
-  if (!GetParam()) {
-    GTEST_SKIP() << "This test requires the virtual card feature.";
-  }
-#endif
   // Test that if the network is American Express and the challenge option
   // denotes that the security code is on the back of the card, its expected
   // length is still 3.
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc
index 36acb0f..bc50c21 100644
--- a/components/autofill/core/common/autofill_payments_features.cc
+++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -314,14 +314,6 @@
 const base::FeatureParam<int> kAutofillUpstreamUpdatedUiTreatment{
     &kAutofillUpstreamUpdatedUi, "autofill_upstream_updated_ui_treatment", 0};
 
-#if BUILDFLAG(IS_IOS)
-// When this is enabled, virtual card enrollment and retrieval will be enabled
-// on Bling.
-BASE_FEATURE(kAutofillEnableVirtualCards,
-             "AutofillEnableVirtualCards",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-#endif
-
 // When enabled, adds a timeout on the network request for VcnEnroll requests.
 BASE_FEATURE(kAutofillVcnEnrollRequestTimeout,
              "AutofillVcnEnrollRequestTimeout",
diff --git a/components/autofill/core/common/autofill_payments_features.h b/components/autofill/core/common/autofill_payments_features.h
index a55e913..d7a364ae 100644
--- a/components/autofill/core/common/autofill_payments_features.h
+++ b/components/autofill/core/common/autofill_payments_features.h
@@ -127,10 +127,6 @@
 COMPONENT_EXPORT(AUTOFILL)
 extern const base::FeatureParam<int> kAutofillUpstreamUpdatedUiTreatment;
 
-#if BUILDFLAG(IS_IOS)
-COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillEnableVirtualCards);
-#endif
-
 COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillVcnEnrollRequestTimeout);
 COMPONENT_EXPORT(AUTOFILL)
diff --git a/components/autofill/ios/browser/autofill_agent.mm b/components/autofill/ios/browser/autofill_agent.mm
index 8a5ca81..1a2a3ffa 100644
--- a/components/autofill/ios/browser/autofill_agent.mm
+++ b/components/autofill/ios/browser/autofill_agent.mm
@@ -352,9 +352,7 @@
   if (suggestion.type == autofill::SuggestionType::kAddressEntry ||
       suggestion.type == autofill::SuggestionType::kCreditCardEntry ||
       suggestion.type == autofill::SuggestionType::kCreateNewPlusAddress ||
-      (base::FeatureList::IsEnabled(
-           autofill::features::kAutofillEnableVirtualCards) &&
-       suggestion.type == autofill::SuggestionType::kVirtualCreditCardEntry) ||
+      suggestion.type == autofill::SuggestionType::kVirtualCreditCardEntry ||
       ((base::FeatureList::IsEnabled(
             autofill::features::kAutofillAddressFieldSwapping) &&
         suggestion.type ==
@@ -595,10 +593,8 @@
     if (popup_suggestion.type == autofill::SuggestionType::kAutocompleteEntry ||
         popup_suggestion.type == autofill::SuggestionType::kAddressEntry ||
         popup_suggestion.type == autofill::SuggestionType::kCreditCardEntry ||
-        (base::FeatureList::IsEnabled(
-             autofill::features::kAutofillEnableVirtualCards) &&
-         popup_suggestion.type ==
-             autofill::SuggestionType::kVirtualCreditCardEntry) ||
+        popup_suggestion.type ==
+            autofill::SuggestionType::kVirtualCreditCardEntry ||
         (base::FeatureList::IsEnabled(
              autofill::features::kAutofillAddressFieldSwapping) &&
          popup_suggestion.type ==
@@ -614,9 +610,7 @@
       // the other elements.
       value = SysUTF16ToNSString(popup_suggestion.main_text.value);
 
-      if (base::FeatureList::IsEnabled(
-              autofill::features::kAutofillEnableVirtualCards) &&
-          (!popup_suggestion.minor_text.value.empty())) {
+      if (!popup_suggestion.minor_text.value.empty()) {
         // For Virtual Cards, the main_text is just "Virtual card" so we need to
         // include the minor_text (which is the card name + last 4 digits ||
         // card holder's name) as the minorValue.
diff --git a/components/autofill/ios/browser/autofill_agent_unittests.mm b/components/autofill/ios/browser/autofill_agent_unittests.mm
index 6097dbe..b5f6b15 100644
--- a/components/autofill/ios/browser/autofill_agent_unittests.mm
+++ b/components/autofill/ios/browser/autofill_agent_unittests.mm
@@ -16,7 +16,6 @@
 #import "base/strings/utf_string_conversions.h"
 #import "base/test/gtest_util.h"
 #import "base/test/ios/wait_util.h"
-#import "base/test/scoped_feature_list.h"
 #import "base/test/test_timeouts.h"
 #import "base/values.h"
 #import "components/autofill/core/browser/autofill_test_utils.h"
@@ -487,10 +486,6 @@
 // is the 'Virtual card' string and the minor_text is the card name + last 4 or
 // the card holder's name
 TEST_F(AutofillAgentTests, showAutofillPopup_ShowVirtualCards) {
-  base::test::ScopedFeatureList feature_list_;
-  feature_list_.InitAndEnableFeature(
-      autofill::features::kAutofillEnableVirtualCards);
-
   __block NSUInteger suggestion_array_size = 0;
   __block FormSuggestion* virtual_card_suggestion = nil;
   __block FormSuggestion* credit_card_suggestion = nil;
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp
index 57eb40187..f6dc34f5 100644
--- a/components/autofill_payments_strings.grdp
+++ b/components/autofill_payments_strings.grdp
@@ -413,18 +413,9 @@
     </message>
   </if>
   <if expr="is_ios">
-    <message name="IDS_AUTOFILL_CARD_UNMASK_PROMPT_NAVIGATION_TITLE" desc="Title for the credit card unmasking dialog navigation bar.">
-      Confirm Card
-    </message>
     <message name="IDS_AUTOFILL_CARD_UNMASK_PROMPT_NAVIGATION_TITLE_VERIFICATION" desc="Title for the IOS navigation bar view when a credit card unmasking related prompt is shown. Such dialog is shown when the user wants to fill a credit card into the form but Chrome needs to finish some verification steps before filling the form.">
       Verification
     </message>
-    <message name="IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS" desc="Text explaining what the user should do in the card unmasking dialog.">
-      Enter the CVC for <ph name="CREDIT_CARD">$1<ex>Visa - 5679</ex></ph>. After you confirm, card details from your Google Account will be shared with this site.
-    </message>
-    <message name="IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_EXPIRED" desc="Text explaining what the user should do in the card unmasking dialog to update an expired card.">
-      Enter the expiration date and CVC for <ph name="CREDIT_CARD">$1<ex>Visa - 5679</ex></ph> to update your card details. After you confirm, card details from your Google Account will be shared with this site.
-    </message>
     <message name="IDS_AUTOFILL_CARD_UNMASK_PROMPT_CVC_FIELD_TITLE" desc="The name of the field for credit card verification code in the card unmasking dialog. The text field where this is presented can be very narrow, so please prefer to translate this to the most common abbreviated form. [CHAR_LIMIT=4]">
       CVC
     </message>
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_PROMPT_NAVIGATION_TITLE.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_PROMPT_NAVIGATION_TITLE.png.sha1
deleted file mode 100644
index 379abddc..0000000
--- a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_PROMPT_NAVIGATION_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-fc752f9e69b28fc6fb956fbddf4f088d8dc22376
\ No newline at end of file
diff --git a/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc b/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc
index 5707a405..c38402c0 100644
--- a/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc
+++ b/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc
@@ -42,20 +42,17 @@
   const base::MemoryMappedFile::Region region = {0, kPersistedFilesizeInBytes};
   base::MemoryMappedFile file;
   int flags = base::File::FLAG_READ | base::File::FLAG_WRITE;
-  if (append) {
-    flags |= base::File::FLAG_OPEN_ALWAYS;
-  } else {
-    flags |= base::File::FLAG_CREATE_ALWAYS;
+  flags |=
+      append ? base::File::FLAG_OPEN_ALWAYS : base::File::FLAG_CREATE_ALWAYS;
+  if (!file.Initialize(base::File(file_path, flags), region,
+                       base::MemoryMappedFile::READ_WRITE_EXTEND)) {
+    return;
   }
-  const bool file_valid =
-      file.Initialize(base::File(file_path, flags), region,
-                      base::MemoryMappedFile::READ_WRITE_EXTEND);
 
-  if (file_valid) {
-    char* data = reinterpret_cast<char*>(file.data());
-    base::strlcpy(&data[position], events.c_str(),
-                  kPersistedFilesizeInBytes - position);
-  }
+  CHECK(position + events.length() <= kPersistedFilesizeInBytes);
+  char* data = reinterpret_cast<char*>(file.data());
+  base::strlcpy(&data[position], events.c_str(),
+                kPersistedFilesizeInBytes - position);
 }
 
 // Returns breadcrumb events stored at |file_path|.
diff --git a/components/device_signals/core/system_signals/mac/mac_platform_delegate.mm b/components/device_signals/core/system_signals/mac/mac_platform_delegate.mm
index 2fde3b4..1aae625 100644
--- a/components/device_signals/core/system_signals/mac/mac_platform_delegate.mm
+++ b/components/device_signals/core/system_signals/mac/mac_platform_delegate.mm
@@ -12,6 +12,7 @@
 
 #import "base/apple/foundation_util.h"
 #include "base/apple/scoped_cftyperef.h"
+#include "base/containers/span.h"
 #include "base/files/file_path.h"
 #include "crypto/sha2.h"
 #include "net/cert/asn1_util.h"
@@ -133,9 +134,7 @@
 
   std::string_view spki_bytes;
   if (!net::asn1::ExtractSPKIFromDERCert(
-          std::string_view(
-              reinterpret_cast<const char*>(CFDataGetBytePtr(der_data.get())),
-              CFDataGetLength(der_data.get())),
+          base::as_string_view(base::apple::CFDataToSpan(der_data.get())),
           &spki_bytes)) {
     return public_keys;
   }
diff --git a/components/feedback/feedback_uploader.cc b/components/feedback/feedback_uploader.cc
index cedada3..432a8ab 100644
--- a/components/feedback/feedback_uploader.cc
+++ b/components/feedback/feedback_uploader.cc
@@ -209,7 +209,7 @@
           destination: GOOGLE_OWNED_SERVICE
           internal {
             contacts {
-              email: "cros-feedback-app@google.com"
+              email: "cros-device-enablement@google.com"
             }
           }
           user_data {
diff --git a/components/flags_ui/resources/app.css b/components/flags_ui/resources/app.css
index bad48ff..5236941 100644
--- a/components/flags_ui/resources/app.css
+++ b/components/flags_ui/resources/app.css
@@ -171,8 +171,8 @@
   /* csschecker-disable-line left-right */
 }
 
-.hidden {
-  display: none;
+[hidden] {
+  display: none !important;
 }
 
 .search-container {
@@ -225,12 +225,12 @@
   outline: 0;
 }
 
-:host(.searching) .clear-search {
+:host([searching]) .clear-search {
   display: inline-block;
 }
 
-:host(.searching) .blurb-container,
-:host(.searching) #promos {
+:host([searching]) .blurb-container,
+:host([searching]) #promos {
   display: none;
 }
 
@@ -458,7 +458,7 @@
     padding: 4px;
   }
 
-  :host(.searching) flags-experiment::part(body) {
+  :host([searching]) flags-experiment::part(body) {
     overflow: visible;
     white-space: normal;
   }
diff --git a/components/flags_ui/resources/app.html b/components/flags_ui/resources/app.html
index 1586178..7e63f67 100644
--- a/components/flags_ui/resources/app.html
+++ b/components/flags_ui/resources/app.html
@@ -44,8 +44,7 @@
       <span class="blurb-warning">$i18n{page-warning}</span>
       <span>$i18n{page-warning-explanation}</span>
 <if expr="chromeos_ash">
-      <p id="owner-warning"
-          ?hidden="${!this.data.showOwnerWarning}">
+      <p id="owner-warning" ?hidden="${!this.data.showOwnerWarning}">
         $i18n{owner-warning}
       </p>
 </if>
@@ -102,7 +101,7 @@
             </flags-experiment>
           `)}
         </div>
-        <div class="no-match hidden" role="alert">$i18n{no-results}</div>
+        <div class="no-match" role="alert" hidden>$i18n{no-results}</div>
       </div>
 <if expr="not is_ios">
       <div id="tab-content-unavailable" class="tab-content"
@@ -114,7 +113,7 @@
             </flags-experiment>
           `)}
         </div>
-        <div class="no-match hidden" role="alert">$i18n{no-results}</div>
+        <div class="no-match" role="alert" hidden>$i18n{no-results}</div>
       </div>
 </if>
     </div>
diff --git a/components/flags_ui/resources/app.ts b/components/flags_ui/resources/app.ts
index bd67806..fe1c8f7 100644
--- a/components/flags_ui/resources/app.ts
+++ b/components/flags_ui/resources/app.ts
@@ -31,82 +31,38 @@
 
 
 /**
+ * Goes through all experiment text and highlights the relevant matches.
+ * Only the first instance of a match in each experiment text block is
+ * highlighted. This prevents the sea of yellow that happens using the
+ * global find in page search.
+ * @param experiments The list of elements to search on and highlight.
+ * @param searchTerm The query to search for.
+ * @return The number of matches found.
+ */
+async function highlightAllMatches(
+    experiments: NodeListOf<FlagsExperimentElement>,
+    searchTerm: string): Promise<number> {
+  let matches = 0;
+  // Not using for..of with async/await to spawn all searching in parallel.
+  await Promise.all(Array.from(experiments).map(async (experiment) => {
+    const hasMatch = await experiment.match(searchTerm);
+    matches += hasMatch ? 1 : 0;
+    experiment.hidden = !hasMatch;
+  }));
+  return matches;
+}
+
+/**
  * Handles in page searching. Matches against the experiment flag name.
  */
 class FlagSearch {
   private flagsAppElement: FlagsAppElement;
-  private initialized: boolean = false;
-  private noMatchMsg: NodeListOf<HTMLElement>;
   private searchIntervalId: number|null = null;
-  private searchBox: HTMLInputElement;
   // Delay in ms following a keypress, before a search is made.
   private searchDebounceDelayMs: number = 150;
 
   constructor(el: FlagsAppElement) {
     this.flagsAppElement = el;
-    this.searchBox =
-        this.flagsAppElement.getRequiredElement<HTMLInputElement>('#search');
-    this.noMatchMsg = this.flagsAppElement.shadowRoot!.querySelectorAll(
-        '.tab-content .no-match');
-  }
-
-  /**
-   * Initialises the in page search. Adds searchbox listeners and
-   * collates the text elements used for string matching.
-   */
-  init() {
-    if (this.initialized) {
-      return;
-    }
-
-    window.addEventListener('keyup', e => {
-      // Check for an active textarea inside a <flags-experiment>.
-      const activeElement = getDeepActiveElement();
-      if (activeElement && activeElement.nodeName === 'TEXTAREA') {
-        return;
-      }
-      switch (e.key) {
-        case '/':
-          this.searchBox.focus();
-          break;
-        case 'Escape':
-          this.searchBox.blur();
-          break;
-      }
-    });
-    this.searchBox.focus();
-    this.initialized = true;
-  }
-
-  /**
-   * Clears a search showing all experiments.
-   */
-  clearSearch() {
-    this.searchBox.value = '';
-    this.doSearch();
-    this.searchBox.focus();
-  }
-
-  /**
-   * Goes through all experiment text and highlights the relevant matches.
-   * Only the first instance of a match in each experiment text block is
-   * highlighted. This prevents the sea of yellow that happens using the
-   * global find in page search.
-   * @param experiments The list of elements to search on and highlight.
-   * @param searchTerm The query to search for.
-   * @return The number of matches found.
-   */
-  private async highlightAllMatches(
-      experiments: NodeListOf<FlagsExperimentElement>,
-      searchTerm: string): Promise<number> {
-    let matches = 0;
-    // Not using for..of with async/await to spawn all searching in parallel.
-    await Promise.all(Array.from(experiments).map(async (experiment) => {
-      const hasMatch = await experiment.match(searchTerm);
-      matches += hasMatch ? 1 : 0;
-      experiment.classList.toggle('hidden', !hasMatch);
-    }));
-    return matches;
   }
 
   /**
@@ -114,63 +70,10 @@
    * permalink text.
    */
   async doSearch() {
-    const searchTerm = this.searchBox.value.trim().toLowerCase();
-
-    this.flagsAppElement.classList.toggle('searching', Boolean(searchTerm));
-
-    // Available experiments
-    const availableExperiments =
-        this.flagsAppElement.shadowRoot!
-            .querySelectorAll<FlagsExperimentElement>(
-                '#tab-content-available flags-experiment');
-    assert(this.noMatchMsg[0]);
-    this.noMatchMsg[0].classList.toggle(
-        'hidden',
-        await this.highlightAllMatches(availableExperiments, searchTerm) > 0);
-
-    // <if expr="not is_ios">
-    // Unavailable experiments, which are undefined on iOS.
-    const unavailableExperiments =
-        this.flagsAppElement.shadowRoot!
-            .querySelectorAll<FlagsExperimentElement>(
-                '#tab-content-unavailable flags-experiment');
-    assert(this.noMatchMsg[1]);
-    this.noMatchMsg[1].classList.toggle(
-        'hidden',
-        await this.highlightAllMatches(unavailableExperiments, searchTerm) > 0);
-    // </if>
-    await this.announceSearchResults();
-    this.flagsAppElement.dispatchEvent(
-        new Event('search-finished-for-testing', {
-          bubbles: true,
-          composed: true,
-        }));
-
+    await this.flagsAppElement.search();
     this.searchIntervalId = null;
   }
 
-  private announceSearchResults(): Promise<void> {
-    const searchTerm = this.searchBox.value.trim().toLowerCase();
-    if (!searchTerm) {
-      return Promise.resolve();
-    }
-
-    const selectedTab = this.flagsAppElement.getTabs().find(
-        tab => tab.panelEl.classList.contains('selected'))!;
-    const selectedTabId = selectedTab.panelEl.id;
-    const queryString = `#${selectedTabId} flags-experiment:not(.hidden)`;
-    const total =
-        this.flagsAppElement.shadowRoot!.querySelectorAll(queryString).length;
-    if (total) {
-      return this.flagsAppElement.announceStatus(
-          total === 1 ?
-              loadTimeData.getStringF('searchResultsSingular', searchTerm) :
-              loadTimeData.getStringF(
-                  'searchResultsPlural', total, searchTerm));
-    }
-    return Promise.resolve();
-  }
-
   /**
    * Debounces the search to improve performance and prevent too many searches
    * from being initiated.
@@ -188,6 +91,12 @@
   }
 }
 
+export interface FlagsAppElement {
+  $: {
+    search: HTMLInputElement,
+  };
+}
+
 export class FlagsAppElement extends CrLitElement {
   static get is() {
     return 'flags-app';
@@ -206,6 +115,11 @@
       data: {type: Object},
       defaultFeatures: {type: Array},
       nonDefaultFeatures: {type: Array},
+
+      searching: {
+        type: Boolean,
+        reflect: true,
+      },
     };
   }
 
@@ -227,6 +141,7 @@
 
   protected defaultFeatures: Feature[] = [];
   protected nonDefaultFeatures: Feature[] = [];
+  protected searching: boolean = false;
 
   private announceStatusDelayMs: number = 100;
   private featuresResolver: PromiseResolver<void> = new PromiseResolver();
@@ -240,6 +155,8 @@
   private isFlagsDeprecatedUrl_: boolean = false;
   // </if>
 
+  private eventTracker_: EventTracker|null = null;
+
   getRequiredElement<K extends keyof HTMLElementTagNameMap>(query: K):
       HTMLElementTagNameMap[K];
   getRequiredElement<E extends HTMLElement = HTMLElement>(query: string): E;
@@ -271,7 +188,8 @@
   override firstUpdated(changedProperties: PropertyValues<this>) {
     super.firstUpdated(changedProperties);
     this.flagSearch = new FlagSearch(this);
-    this.flagSearch.init();
+
+    this.$.search.focus();
   }
 
   override updated(changedProperties: PropertyValues<this>) {
@@ -317,8 +235,6 @@
     this.requestExperimentalFeaturesData();
 
     FocusOutlineManager.forDocument(document);
-    // Update the highlighted flag when the hash changes.
-    window.addEventListener('hashchange', () => this.highlightReferencedFlag);
 
     // <if expr="not is_ios">
     if (this.isFlagsDeprecatedUrl_) {
@@ -330,13 +246,42 @@
       this.getRequiredElement('.blurb-warning').textContent = '';
       this.getRequiredElement('.blurb-warning + span').textContent =
           loadTimeData.getString('deprecatedPageWarningExplanation');
-      this.getRequiredElement<HTMLInputElement>('#search').placeholder =
+      this.$.search.placeholder =
           loadTimeData.getString('deprecatedSearchPlaceholder');
       for (const element of this.shadowRoot!.querySelectorAll('.no-match')) {
         element.textContent = loadTimeData.getString('deprecatedNoResults');
       }
     }
     // </if>
+
+    this.eventTracker_ = new EventTracker();
+
+    // Update the highlighted flag when the hash changes.
+    this.eventTracker_.add(
+        window, 'hashchange', () => this.highlightReferencedFlag());
+
+    this.eventTracker_.add(window, 'keyup', (e: KeyboardEvent) => {
+      // Check for an active textarea inside a <flags-experiment>.
+      const activeElement = getDeepActiveElement();
+      if (activeElement && activeElement.nodeName === 'TEXTAREA') {
+        return;
+      }
+      switch (e.key) {
+        case '/':
+          this.$.search.focus();
+          break;
+        case 'Escape':
+          this.$.search.blur();
+          break;
+      }
+    });
+  }
+
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+    assert(this.eventTracker_);
+    this.eventTracker_.removeAll();
+    this.eventTracker_ = null;
   }
 
   setAnnounceStatusDelayMsForTesting(delay: number) {
@@ -367,6 +312,65 @@
     });
   }
 
+  async search() {
+    const searchTerm = this.$.search.value.trim().toLowerCase();
+
+    this.searching = Boolean(searchTerm);
+
+    // Available experiments
+    const availableExperiments =
+        this.shadowRoot!.querySelectorAll<FlagsExperimentElement>(
+            '#tab-content-available flags-experiment');
+    const availableExperimentsHits =
+        await highlightAllMatches(availableExperiments, searchTerm);
+    let noMatchMsg =
+        this.getRequiredElement('#tab-content-available .no-match');
+    noMatchMsg.toggleAttribute('hidden', availableExperimentsHits > 0);
+
+    // <if expr="not is_ios">
+    // Unavailable experiments, which are undefined on iOS.
+    const unavailableExperiments =
+        this.shadowRoot!.querySelectorAll<FlagsExperimentElement>(
+            '#tab-content-unavailable flags-experiment');
+    const unavailableExperimentsHits =
+        await highlightAllMatches(unavailableExperiments, searchTerm);
+    noMatchMsg = this.getRequiredElement('#tab-content-unavailable .no-match');
+    noMatchMsg.toggleAttribute('hidden', unavailableExperimentsHits > 0);
+    // </if>
+
+    if (this.searching) {
+      // <if expr="is_ios">
+      await this.announceSearchResults(searchTerm, availableExperimentsHits);
+      // </if>
+      // <if expr="not is_ios">
+      const selectedTabContentId =
+          this.getRequiredElement('.tab-content.selected').id;
+      const hits = selectedTabContentId === 'tab-content-available' ?
+          availableExperimentsHits :
+          unavailableExperimentsHits;
+      await this.announceSearchResults(searchTerm, hits);
+      // </if>
+    }
+
+    await this.updateComplete;
+    this.dispatchEvent(new Event('search-finished-for-testing', {
+      bubbles: true,
+      composed: true,
+    }));
+  }
+
+  private announceSearchResults(searchTerm: string, total: number):
+      Promise<void> {
+    if (total) {
+      return this.announceStatus(
+          total === 1 ?
+              loadTimeData.getStringF('searchResultsSingular', searchTerm) :
+              loadTimeData.getStringF(
+                  'searchResultsPlural', total, searchTerm));
+    }
+    return Promise.resolve();
+  }
+
   /**
    * Toggles necessary attributes to display selected tab.
    */
@@ -448,6 +452,16 @@
     this.data = data;
   }
 
+  /**
+   * Clears a search showing all experiments.
+   */
+  private clearSearch() {
+    this.$.search.value = '';
+    assert(this.flagSearch);
+    this.flagSearch.doSearch();
+    this.$.search.focus();
+  }
+
   /** Reset all flags to their default values and refresh the UI. */
   protected async onResetAllClick_(e: Event) {
     this.lastChanged = e.target as HTMLElement;
@@ -458,8 +472,7 @@
     await this.requestExperimentalFeaturesData();
     await this.updateComplete;
 
-    assert(this.flagSearch);
-    this.flagSearch.clearSearch();
+    this.clearSearch();
   }
 
   protected onSearchInput_() {
@@ -468,8 +481,7 @@
   }
 
   protected onClearSearchClick_() {
-    assert(this.flagSearch);
-    this.flagSearch.clearSearch();
+    this.clearSearch();
   }
 
   protected onSelectChange_(e: Event) {
diff --git a/components/flags_ui/resources/experiment.ts b/components/flags_ui/resources/experiment.ts
index 1aaf649..799574b 100644
--- a/components/flags_ui/resources/experiment.ts
+++ b/components/flags_ui/resources/experiment.ts
@@ -234,7 +234,7 @@
     // Populate clone elements with the proper text content.
     queries.forEach((query, i) => {
       const clone = this.getRequiredElement(query);
-      clone.textContent = itemsToSearch[i]!;
+      clone.textContent = (i === 3 ? '#' : '') + itemsToSearch[i]!;
     });
 
     // Add highlights to the first clone element with matches.
diff --git a/components/history_embeddings/history_embeddings_features.cc b/components/history_embeddings/history_embeddings_features.cc
index ce3723c..c74cd705 100644
--- a/components/history_embeddings/history_embeddings_features.cc
+++ b/components/history_embeddings/history_embeddings_features.cc
@@ -181,6 +181,10 @@
     "WordMatchSmoothingFactor",
     1);
 
+const base::FeatureParam<int> kWordMatchMaxTermCount(&kHistoryEmbeddings,
+                                                     "WordMatchMaxTermCount",
+                                                     3);
+
 bool IsHistoryEmbeddingsEnabled() {
 #if BUILDFLAG(IS_CHROMEOS)
   return chromeos::features::IsFeatureManagementHistoryEmbeddingEnabled() &&
diff --git a/components/history_embeddings/history_embeddings_features.h b/components/history_embeddings/history_embeddings_features.h
index 62b487e..9524251d 100644
--- a/components/history_embeddings/history_embeddings_features.h
+++ b/components/history_embeddings/history_embeddings_features.h
@@ -143,6 +143,7 @@
 extern const base::FeatureParam<double> kWordMatchScoreBoostFactor;
 extern const base::FeatureParam<int> kWordMatchLimit;
 extern const base::FeatureParam<int> kWordMatchSmoothingFactor;
+extern const base::FeatureParam<int> kWordMatchMaxTermCount;
 
 // Whether the history embeddings feature is enabled. This only checks if the
 // feature flags are enabled and does not check the user's opt-in preference.
diff --git a/components/history_embeddings/history_embeddings_service.cc b/components/history_embeddings/history_embeddings_service.cc
index 68a7765..c672c97 100644
--- a/components/history_embeddings/history_embeddings_service.cc
+++ b/components/history_embeddings/history_embeddings_service.cc
@@ -411,6 +411,12 @@
   search_params.word_match_limit = kWordMatchLimit.Get();
   search_params.word_match_smoothing_factor = kWordMatchSmoothingFactor.Get();
 
+  if (search_params.query_terms.size() >
+      static_cast<size_t>(kWordMatchMaxTermCount.Get())) {
+    // Disable word match boosting for this long query.
+    search_params.query_terms.clear();
+  }
+
   embedder_->ComputePassagesEmbeddings(
       PassageKind::QUERY, {std::move(query)},
       base::BindOnce(&HistoryEmbeddingsService::OnQueryEmbeddingComputed,
diff --git a/components/history_embeddings/history_embeddings_service_unittest.cc b/components/history_embeddings/history_embeddings_service_unittest.cc
index 264f39ce..0cdb6ec1 100644
--- a/components/history_embeddings/history_embeddings_service_unittest.cc
+++ b/components/history_embeddings/history_embeddings_service_unittest.cc
@@ -90,7 +90,9 @@
   void SetUp() override {
     feature_list_.InitWithFeaturesAndParameters(
         {{kHistoryEmbeddings,
-          {{"SearchPassageMinimumWordCount", "3"}, {"EnableAnswers", "true"}}},
+          {{"SearchPassageMinimumWordCount", "3"},
+           {"EnableAnswers", "true"},
+           {"WordMatchMinEmbeddingScore", "0"}}},
 #if BUILDFLAG(IS_CHROMEOS)
          {chromeos::features::kFeatureManagementHistoryEmbedding, {{}}}
 #endif  // BUILDFLAG(IS_CHROMEOS)
@@ -456,7 +458,8 @@
     feature_list_.Reset();
     feature_list_.InitAndEnableFeatureWithParameters(
         kHistoryEmbeddings, {{"SearchPassageMinimumWordCount", "3"},
-                             {"SearchScoreThreshold", "0.5"}});
+                             {"SearchScoreThreshold", "0.5"},
+                             {"WordMatchMinEmbeddingScore", "0"}});
     base::test::TestFuture<SearchResult> future;
     service_->OnSearchCompleted(future.GetRepeatingCallback(), {},
                                 scored_url_rows);
@@ -679,4 +682,41 @@
             std::vector<std::string>({"stop", "words"}));
 }
 
+TEST_F(HistoryEmbeddingsServiceTest, SearchDoesNotWordMatchBoostLongQueries) {
+  AddTestHistoryPage("http://test1.com");
+  OverrideVisibilityScoresForTesting({
+      {"boosted test query", 0.99},
+      {"this very long test query isn't boosted", 0.99},
+      {"test passage 1", 0.99},
+      {"test passage 2", 0.99},
+  });
+  OnPassagesEmbeddingsComputed(UrlPassages(1, 1, base::Time::Now()),
+                               {"test passage 1", "test passage 2"},
+                               {Embedding(std::vector<float>(768, 1.0f)),
+                                Embedding(std::vector<float>(768, 1.0f))},
+                               ComputeEmbeddingsStatus::SUCCESS);
+  {
+    base::test::TestFuture<SearchResult> future;
+    service_->Search(/*previous_search_result=*/nullptr, "boosted test query",
+                     {}, 1, future.GetRepeatingCallback());
+    SearchResult result = future.Take();
+    EXPECT_EQ(result.scored_url_rows.size(), 1u);
+    const ScoredUrlRow& row = result.scored_url_rows[0];
+    // The word "test" in "boosted test query" boosts the score slightly.
+    EXPECT_LT(std::ranges::max(row.scores), row.scored_url.score);
+  }
+  {
+    base::test::TestFuture<SearchResult> future;
+    service_->Search(/*previous_search_result=*/nullptr,
+                     "this very long test query isn't boosted", {}, 1,
+                     future.GetRepeatingCallback());
+    SearchResult result = future.Take();
+    EXPECT_EQ(result.scored_url_rows.size(), 1u);
+    const ScoredUrlRow& row = result.scored_url_rows[0];
+    // The word "test" makes no difference in the long query because
+    // there are enough terms to disable word match boosting.
+    EXPECT_FLOAT_EQ(std::ranges::max(row.scores), row.scored_url.score);
+  }
+}
+
 }  // namespace history_embeddings
diff --git a/components/history_embeddings/ml_answerer.cc b/components/history_embeddings/ml_answerer.cc
index 6d5aacb..ab7eca3 100644
--- a/components/history_embeddings/ml_answerer.cc
+++ b/components/history_embeddings/ml_answerer.cc
@@ -68,8 +68,8 @@
   ~SessionManager() {
     // Run the existing callback if not called yet with canceled status.
     if (!callback_.is_null()) {
-      Finish(AnswererResult(ComputeAnswerStatus::EXECUTION_CANCELLED, query_,
-                            Answer()));
+      FinishAndResetSessions(AnswererResult(
+          ComputeAnswerStatus::EXECUTION_CANCELLED, query_, Answer()));
     }
   }
 
@@ -104,12 +104,17 @@
 
   size_t GetNumberOfSessions() { return sessions_.size(); }
 
-  // Finishes and cleans up sessions.
-  void Finish(AnswererResult answer_result) {
+  // Runs callback with result.
+  void FinishCallback(AnswererResult answer_result) {
     CHECK(!callback_.is_null());
     origin_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback_), std::move(answer_result)));
+  }
+
+  // Finishes and cleans up sessions.
+  void FinishAndResetSessions(AnswererResult answer_result) {
+    FinishCallback(std::move(answer_result));
 
     // Destroy all existing sessions.
     sessions_.clear();
@@ -123,15 +128,15 @@
       optimization_guide::OptimizationGuideModelStreamingExecutionResult
           result) {
     if (!result.response.has_value()) {
-      Finish(AnswererResult(ComputeAnswerStatus::EXECUTION_FAILURE, query_,
-                            Answer()));
+      FinishCallback(AnswererResult(ComputeAnswerStatus::EXECUTION_FAILURE,
+                                    query_, Answer()));
     } else if (result.response->is_complete) {
       auto response = optimization_guide::ParsedAnyMetadata<
           optimization_guide::proto::HistoryAnswerResponse>(
           std::move(result.response).value().response);
-      Finish(AnswererResult(ComputeAnswerStatus::SUCCESS, query_,
-                            response->answer(), std::move(result.log_entry),
-                            urls_[session_index], {}));
+      FinishCallback(AnswererResult(
+          ComputeAnswerStatus::SUCCESS, query_, response->answer(),
+          std::move(result.log_entry), urls_[session_index], {}));
     }
   }
 
@@ -149,7 +154,7 @@
 
     // Return unanswerable status due to highest score is below the threshold.
     if (max_score < GetMlAnswerScoreThreshold()) {
-      Finish(
+      FinishAndResetSessions(
           AnswererResult{ComputeAnswerStatus::UNANSWERABLE, query_, Answer()});
       return;
     }
@@ -166,8 +171,8 @@
                               weak_ptr_factory_.GetWeakPtr(), session_index));
     } else {
       // If sessions are already cleaned up, run callback with canceled status.
-      Finish(AnswererResult{ComputeAnswerStatus::EXECUTION_CANCELLED, query_,
-                            Answer()});
+      FinishAndResetSessions(AnswererResult{
+          ComputeAnswerStatus::EXECUTION_CANCELLED, query_, Answer()});
     }
   }
 
@@ -207,7 +212,7 @@
         optimization_guide::ModelBasedCapabilityKey::kHistorySearch,
         /*config_params=*/std::nullopt);
     if (session == nullptr) {
-      session_manager_->Finish(AnswererResult(
+      session_manager_->FinishAndResetSessions(AnswererResult(
           ComputeAnswerStatus::MODEL_UNAVAILABLE, query, Answer()));
       return;
     }
diff --git a/components/lens/lens_features.cc b/components/lens/lens_features.cc
index 64dbbe1b..c5fa04a 100644
--- a/components/lens/lens_features.cc
+++ b/components/lens/lens_features.cc
@@ -63,6 +63,10 @@
              "LensOverlayContextualSearchbox",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kLensOverlaySurvey,
+             "LensOverlaySurvey",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 const base::FeatureParam<int> kLensOverlayMinRamMb{&kLensOverlay, "min_ram_mb",
                                                    /*default=value=*/-1};
 const base::FeatureParam<std::string> kActivityUrl{
@@ -270,6 +274,9 @@
     &kLensOverlayContextualSearchbox, "cluster-info-endpoint-url",
     "https://lensfrontend-pa.googleapis.com/v1/gsessionid"};
 
+const base::FeatureParam<base::TimeDelta> kLensOverlaySurveyResultsTime{
+    &kLensOverlaySurvey, "results-time", base::Seconds(1)};
+
 constexpr base::FeatureParam<std::string> kHomepageURLForLens{
     &kLensStandalone, "lens-homepage-url", "https://lens.google.com/v3/"};
 
@@ -682,4 +689,8 @@
   return base::FeatureList::IsEnabled(kLensOverlayContextualSearchbox);
 }
 
+base::TimeDelta GetLensOverlaySurveyResultsTime() {
+  return kLensOverlaySurveyResultsTime.Get();
+}
+
 }  // namespace lens::features
diff --git a/components/lens/lens_features.h b/components/lens/lens_features.h
index f13f95c..14008431 100644
--- a/components/lens/lens_features.h
+++ b/components/lens/lens_features.h
@@ -59,6 +59,10 @@
 COMPONENT_EXPORT(LENS_FEATURES)
 BASE_DECLARE_FEATURE(kLensOverlayContextualSearchbox);
 
+// Enables the Lens overlay HaTS survey.
+COMPONENT_EXPORT(LENS_FEATURES)
+BASE_DECLARE_FEATURE(kLensOverlaySurvey);
+
 // The base URL for Lens.
 COMPONENT_EXPORT(LENS_FEATURES)
 extern const base::FeatureParam<std::string> kHomepageURLForLens;
@@ -560,6 +564,10 @@
 COMPONENT_EXPORT(LENS_FEATURES)
 extern bool IsLensOverlayContextualSearchboxEnabled();
 
+// Time delay for the results trigger of the Lens Overlay HaTS survey.
+COMPONENT_EXPORT(LENS_FEATURES)
+extern base::TimeDelta GetLensOverlaySurveyResultsTime();
+
 }  // namespace lens::features
 
 #endif  // COMPONENTS_LENS_LENS_FEATURES_H_
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc
index 88de03e..a86d45f 100644
--- a/components/omnibox/browser/autocomplete_match.cc
+++ b/components/omnibox/browser/autocomplete_match.cc
@@ -70,7 +70,12 @@
 #if (!BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !BUILDFLAG(IS_IOS)
 // Used for `SEARCH_SUGGEST_TAIL` and `NULL_RESULT_MESSAGE` (e.g. starter pack)
 // type suggestion icons.
-static gfx::VectorIcon empty_icon;
+
+const gfx::VectorIcon& GetEmptyIcon() {
+  static const gfx::VectorIcon instance;
+  return instance;
+}
+
 #endif
 
 bool IsTrivialClassification(const ACMatchClassifications& classifications) {
@@ -582,7 +587,7 @@
       // Found), fallthrough to use the empty icon.
       switch (iph_type) {
         case IphType::kNone:
-          return empty_icon;
+          return GetEmptyIcon();
         case IphType::kGemini:
           return omnibox::kSparkIcon;
         case IphType::kFeaturedEnterpriseSearch:
@@ -590,7 +595,7 @@
         case IphType::kHistoryEmbeddingsSettingsPromo:
           return omnibox::kSparkIcon;
         case IphType::kHistoryEmbeddingsDisclaimer:
-          return empty_icon;
+          return GetEmptyIcon();
         case IphType::kHistoryScopePromo:
           return vector_icons::kHistoryChromeRefreshIcon;
         case IphType::kHistoryEmbeddingsScopePromo:
@@ -598,7 +603,7 @@
       }
 
     case Type::SEARCH_SUGGEST_TAIL:
-      return empty_icon;
+      return GetEmptyIcon();
 
     case Type::DOCUMENT_SUGGESTION:
       switch (document_type) {
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index 0fac8bd..6944d985 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -384,15 +384,6 @@
              "AblateSearchProviderWarmup",
              DISABLED);
 
-// When enabled, removes unrecognized TemplateURL parameters, rather than
-// keeping them verbatim. This feature will ensure that the new versions of
-// Chrome will properly behave when supplied with Template URLs featuring
-// unknown parameters; rather than inlining the verbatim unexpanded placeholder,
-// the placeholder will be replaced with an empty string.
-BASE_FEATURE(kDropUnrecognizedTemplateUrlParameters,
-             "DropUnrecognizedTemplateUrlParameters",
-             DISABLED);
-
 // If enabled, hl= is reported in search requests (applicable to iOS only).
 BASE_FEATURE(kReportApplicationLanguageInSearchRequest,
              "ReportApplicationLanguageInSearchRequest",
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index 934f85d6..f18818a 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -136,7 +136,6 @@
 
 // Search and Suggest requests and params.
 BASE_DECLARE_FEATURE(kAblateSearchProviderWarmup);
-BASE_DECLARE_FEATURE(kDropUnrecognizedTemplateUrlParameters);
 BASE_DECLARE_FEATURE(kReportApplicationLanguageInSearchRequest);
 
 BASE_DECLARE_FEATURE(kOmniboxAsyncViewInflation);
diff --git a/components/optimization_guide/core/model_quality/feature_type_map.h b/components/optimization_guide/core/model_quality/feature_type_map.h
index 3d43d14..dc08093 100644
--- a/components/optimization_guide/core/model_quality/feature_type_map.h
+++ b/components/optimization_guide/core/model_quality/feature_type_map.h
@@ -120,6 +120,7 @@
 class FormsAnnotationsFeatureTypeMap {
  public:
   using LoggingData = proto::FormsAnnotationsLoggingData;
+  using Quality = proto::FormsAnnotationsQuality;
 
   static LoggingData* GetLoggingData(proto::LogAiDataRequest& ai_data_request) {
     return ai_data_request.mutable_forms_annotations();
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index 150801e..b359dced 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit 150801e3570d70b31bac149381371b0ae6326666
+Subproject commit b359dced1380042078bf7191e24a4b9bf1c5d61e
diff --git a/components/optimization_guide/proto/features/forms_annotations.proto b/components/optimization_guide/proto/features/forms_annotations.proto
index aacb31ec..7dcfcb8 100644
--- a/components/optimization_guide/proto/features/forms_annotations.proto
+++ b/components/optimization_guide/proto/features/forms_annotations.proto
@@ -23,6 +23,8 @@
   FormsAnnotationsRequest request = 1;
 
   FormsAnnotationsResponse response = 2;
+
+  FormsAnnotationsQuality quality = 3;
 }
 
 message FormsAnnotationsRequest {
@@ -47,3 +49,7 @@
   // The user annotations entry IDs that were deleted.
   repeated int64 deleted_entry_ids = 3;
 }
+
+message FormsAnnotationsQuality {
+  UserFeedback user_feedback = 1;
+}
diff --git a/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.cc
index 7f289963..23ab843 100644
--- a/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.cc
@@ -345,6 +345,7 @@
           {WebFeature::kCreateCSSModuleScript, WebDXFeature::kCssModules},
           {WebFeature::kStreamingDeclarativeShadowDOM,
            WebDXFeature::kDeclarativeShadowDom},
+          {WebFeature::kShowPickerSelect, WebDXFeature::kShowPickerSelect},
           {WebFeature::kHiddenUntilFoundAttribute,
            WebDXFeature::kHiddenUntilFoundAttribute},
           {WebFeature::kHTMLDetailsElementNameAttribute,
diff --git a/components/password_manager/core/browser/password_store/login_database_ios.cc b/components/password_manager/core/browser/password_store/login_database_ios.cc
index 5e18c748..ed0b24d 100644
--- a/components/password_manager/core/browser/password_store/login_database_ios.cc
+++ b/components/password_manager/core/browser/password_store/login_database_ios.cc
@@ -15,6 +15,7 @@
 #include "base/apple/scoped_cftyperef.h"
 #include "base/base64.h"
 #include "base/containers/heap_array.h"
+#include "base/containers/span.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/sys_string_conversions.h"
@@ -131,13 +132,8 @@
   }
 
   CFDataRef data = base::apple::CFCast<CFDataRef>(data_cftype.get());
-  const size_t size = CFDataGetLength(data);
-  auto buffer = base::HeapArray<UInt8>::Uninit(size);
-  CFDataGetBytes(data, CFRangeMake(0, size), buffer.data());
-
-  *plain_text = base::UTF8ToUTF16(
-      std::string(static_cast<char*>(static_cast<void*>(buffer.data())),
-                  static_cast<size_t>(size)));
+  *plain_text =
+      base::UTF8ToUTF16(base::as_string_view(base::apple::CFDataToSpan(data)));
   return errSecSuccess;
 }
 
@@ -199,12 +195,8 @@
 
     if (CFDataRef data = base::apple::GetValueFromDictionary<CFDataRef>(
             dict, kSecValueData)) {
-      const size_t size = CFDataGetLength(data);
-      std::vector<UInt8> buffer(size);
-      CFDataGetBytes(data, CFRangeMake(0, size), buffer.data());
-
-      std::u16string plain_text = base::UTF8ToUTF16(std::string_view(
-          static_cast<char*>(static_cast<void*>(buffer.data())), size));
+      std::u16string plain_text = base::UTF8ToUTF16(
+          base::as_string_view(base::apple::CFDataToSpan(data)));
       key_password_pairs->emplace(key, std::move(plain_text));
     }
   }
diff --git a/components/performance_manager/BUILD.gn b/components/performance_manager/BUILD.gn
index 90b61eb..aae171b 100644
--- a/components/performance_manager/BUILD.gn
+++ b/components/performance_manager/BUILD.gn
@@ -200,7 +200,6 @@
     "public/resource_attribution/resource_types.h",
     "public/resource_attribution/type_helpers.h",
     "public/resource_attribution/worker_context.h",
-    "public/scenarios/loading_scenario_observer.h",
     "public/scenarios/performance_scenarios.h",
     "public/user_tuning/prefs.h",
     "public/user_tuning/tab_revisit_tracker.h",
@@ -245,6 +244,7 @@
     "resource_attribution/worker_client_pages.h",
     "resource_attribution/worker_context.cc",
     "scenarios/loading_scenario_observer.cc",
+    "scenarios/loading_scenario_observer.h",
     "scenarios/performance_scenarios.cc",
     "service_worker_context_adapter.cc",
     "service_worker_context_adapter.h",
diff --git a/components/performance_manager/embedder/graph_features.h b/components/performance_manager/embedder/graph_features.h
index e22963c3..b964ccd 100644
--- a/components/performance_manager/embedder/graph_features.h
+++ b/components/performance_manager/embedder/graph_features.h
@@ -7,6 +7,9 @@
 
 #include <cstdint>
 
+#include "base/feature_list.h"
+#include "components/performance_manager/public/features.h"
+
 namespace performance_manager {
 
 class Graph;
@@ -43,6 +46,7 @@
       bool frame_visibility_decorator : 1;
       bool frozen_frame_aggregator : 1;
       bool important_frame_decorator : 1;
+      bool loading_scenario : 1;
       bool metrics_collector : 1;
       bool node_impl_describers : 1;
       bool page_aggregator : 1;
@@ -75,6 +79,11 @@
     return *this;
   }
 
+  constexpr GraphFeatures& EnableLoadingScenario() {
+    flags_.loading_scenario = true;
+    return *this;
+  }
+
   constexpr GraphFeatures& EnableMetricsCollector() {
     flags_.metrics_collector = true;
     return *this;
diff --git a/components/performance_manager/features.cc b/components/performance_manager/features.cc
index 105c80c..4e2b4fb 100644
--- a/components/performance_manager/features.cc
+++ b/components/performance_manager/features.cc
@@ -183,4 +183,8 @@
              "UnimportantFramesPriority",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kLoadingPerformanceScenario,
+             "LoadingPerformanceScenario",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace performance_manager::features
diff --git a/components/performance_manager/graph_features.cc b/components/performance_manager/graph_features.cc
index eed70b9..eba6b5ab 100644
--- a/components/performance_manager/graph_features.cc
+++ b/components/performance_manager/graph_features.cc
@@ -23,6 +23,7 @@
 #include "components/performance_manager/public/graph/graph.h"
 #include "components/performance_manager/public/metrics/metrics_collector.h"
 #include "components/performance_manager/resource_attribution/query_scheduler.h"
+#include "components/performance_manager/scenarios/loading_scenario_observer.h"
 #include "components/performance_manager/v8_memory/v8_context_tracker.h"
 
 #if !BUILDFLAG(IS_ANDROID)
@@ -75,6 +76,9 @@
   if (flags_.frozen_frame_aggregator) {
     Install<FrozenFrameAggregator>(graph);
   }
+  if (flags_.loading_scenario) {
+    Install<LoadingScenarioObserver>(graph);
+  }
   if (flags_.resource_attribution_scheduler) {
     Install<resource_attribution::internal::QueryScheduler>(graph);
   }
diff --git a/components/performance_manager/public/features.h b/components/performance_manager/public/features.h
index 5ce1faf0..bb066f7 100644
--- a/components/performance_manager/public/features.h
+++ b/components/performance_manager/public/features.h
@@ -173,6 +173,10 @@
 // non unimportant frames.
 BASE_DECLARE_FEATURE(kUnimportantFramesPriority);
 
+// When enabled, PerformanceManager will update
+// blink::performance_scenarios::LoadingScenario.
+BASE_DECLARE_FEATURE(kLoadingPerformanceScenario);
+
 }  // namespace performance_manager::features
 
 #endif  // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_FEATURES_H_
diff --git a/components/performance_manager/scenarios/loading_scenario_observer.cc b/components/performance_manager/scenarios/loading_scenario_observer.cc
index ea734a2..cdc8681 100644
--- a/components/performance_manager/scenarios/loading_scenario_observer.cc
+++ b/components/performance_manager/scenarios/loading_scenario_observer.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/performance_manager/public/scenarios/loading_scenario_observer.h"
+#include "components/performance_manager/scenarios/loading_scenario_observer.h"
 
 #include <atomic>
 
diff --git a/components/performance_manager/public/scenarios/loading_scenario_observer.h b/components/performance_manager/scenarios/loading_scenario_observer.h
similarity index 90%
rename from components/performance_manager/public/scenarios/loading_scenario_observer.h
rename to components/performance_manager/scenarios/loading_scenario_observer.h
index b68eef62..c34072d 100644
--- a/components/performance_manager/public/scenarios/loading_scenario_observer.h
+++ b/components/performance_manager/scenarios/loading_scenario_observer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_SCENARIOS_LOADING_SCENARIO_OBSERVER_H_
-#define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_SCENARIOS_LOADING_SCENARIO_OBSERVER_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_SCENARIOS_LOADING_SCENARIO_OBSERVER_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_SCENARIOS_LOADING_SCENARIO_OBSERVER_H_
 
 #include "base/sequence_checker.h"
 #include "components/performance_manager/public/graph/graph.h"
@@ -71,4 +71,4 @@
 
 }  // namespace performance_manager
 
-#endif  // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_SCENARIOS_LOADING_SCENARIO_OBSERVER_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_SCENARIOS_LOADING_SCENARIO_OBSERVER_H_
diff --git a/components/performance_manager/scenarios/loading_scenario_observer_unittest.cc b/components/performance_manager/scenarios/loading_scenario_observer_unittest.cc
index 88b074b..036acac9 100644
--- a/components/performance_manager/scenarios/loading_scenario_observer_unittest.cc
+++ b/components/performance_manager/scenarios/loading_scenario_observer_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/performance_manager/public/scenarios/loading_scenario_observer.h"
+#include "components/performance_manager/scenarios/loading_scenario_observer.h"
 
 #include <atomic>
 #include <memory>
diff --git a/components/search_engines/template_url.cc b/components/search_engines/template_url.cc
index abb7c61..719227f 100644
--- a/components/search_engines/template_url.cc
+++ b/components/search_engines/template_url.cc
@@ -717,8 +717,6 @@
   const auto parameter =
       base::MakeStringPiece(original_url.begin() + start + 1,
                             original_url.begin() + start + 1 + length);
-  const auto full_parameter = base::MakeStringPiece(
-      original_url.begin() + start, original_url.begin() + end + 1);
   // Remove the parameter from the string.  For parameters who replacement is
   // constant and already known, just replace them directly.  For other cases,
   // like parameters whose values may change over time, use |replacements|.
@@ -839,26 +837,6 @@
     // We don't support these.
     if (!optional)
       url->insert(start, "1");
-  } else if (!prepopulated_) {
-    base::UmaHistogramBoolean("Omnibox.TemplateUrl.UnrecognizedParameter",
-                              /* is externally supplied template? */ false);
-    // Note: in certain scenarios this function is tested before FeatureFlag is
-    // initialized. Check whether FeatureList has been instantiated before
-    // testing the flag to avoid talking to EarlyFeatureAccessTracker.
-    if (!base::FeatureList::GetInstance() ||
-        !base::FeatureList::IsEnabled(
-            omnibox::kDropUnrecognizedTemplateUrlParameters)) {
-      url->insert(start, full_parameter.data(), full_parameter.size());
-      return false;
-    }
-    // The unrecognized parameters can originate from Chrome's DMA upstream
-    // definitions, or Chrome Extensions. In each case, data has shown that the
-    // parameters don't carry any JSON or Javascript content and should be safe
-    // to be removed.
-    // This still allows JSON payload to be included in the Template URL, but
-    // requires the braces to be escaped ahead of time.
-    //
-    // Fallthrough.
   } else {
     // Despite Chrome normally relying on prepopulated_engines.json file, there
     // are other mechanisms that can supply overrides - see:
@@ -870,7 +848,7 @@
     //
     // Fallthrough.
     base::UmaHistogramBoolean("Omnibox.TemplateUrl.UnrecognizedParameter",
-                              /* is externally supplied template? */ true);
+                              prepopulated_);
   }
   return true;
 }
diff --git a/components/search_engines/template_url_unittest.cc b/components/search_engines/template_url_unittest.cc
index a5596b7..ee38255 100644
--- a/components/search_engines/template_url_unittest.cc
+++ b/components/search_engines/template_url_unittest.cc
@@ -231,8 +231,7 @@
   const TemplateURLRef::PostParams& bad_post_params =
       url_bad.image_url_ref().post_params_;
   ASSERT_EQ(2U, bad_post_params.size());
-  ExpectPostParamIs(bad_post_params[0], "unknown_template",
-                    "{UnknownTemplate}");
+  ExpectPostParamIs(bad_post_params[0], "unknown_template", "");
   ExpectPostParamIs(bad_post_params[1], "bad_value", "bad{value}");
 #endif
 
@@ -469,7 +468,7 @@
   TemplateURLRef::Replacements replacements;
   bool valid = false;
 
-  EXPECT_EQ("http://foo{fhqwhgads}bar",
+  EXPECT_EQ("http://foobar",
             url.url_ref().ParseURL("http://foo{fhqwhgads}bar", &replacements,
                                    nullptr, &valid));
   EXPECT_TRUE(valid);
@@ -500,21 +499,21 @@
   const SearchTermsData& stdata = search_terms_data_;
 
   TemplateURL url(data);
-  EXPECT_EQ("http://foo{fhqwhgads}search/?q=X",
+  EXPECT_EQ("http://foosearch/?q=X",
             url.url_ref().ReplaceSearchTerms(args, stdata));
-  EXPECT_EQ("http://foo{fhqwhgads}alternate/?q=X",
+  EXPECT_EQ("http://fooalternate/?q=X",
             url.url_refs()[0].ReplaceSearchTerms(args, stdata));
-  EXPECT_EQ("http://foo{fhqwhgads}search/?q=X",
+  EXPECT_EQ("http://foosearch/?q=X",
             url.url_refs()[1].ReplaceSearchTerms(args, stdata));
-  EXPECT_EQ("http://foo{fhqwhgads}suggest/?q=X",
+  EXPECT_EQ("http://foosuggest/?q=X",
             url.suggestions_url_ref().ReplaceSearchTerms(args, stdata));
-  EXPECT_EQ("http://foo{fhqwhgads}image/",
+  EXPECT_EQ("http://fooimage/",
             url.image_url_ref().ReplaceSearchTerms(args, stdata));
-  EXPECT_EQ("http://foo{fhqwhgads}image/?translate",
+  EXPECT_EQ("http://fooimage/?translate",
             url.image_translate_url_ref().ReplaceSearchTerms(args, stdata));
-  EXPECT_EQ("http://foo{fhqwhgads}newtab/",
+  EXPECT_EQ("http://foonewtab/",
             url.new_tab_url_ref().ReplaceSearchTerms(args, stdata));
-  EXPECT_EQ("http://foo{fhqwhgads}context/",
+  EXPECT_EQ("http://foocontext/",
             url.contextual_search_url_ref().ReplaceSearchTerms(args, stdata));
 
   data.prepopulate_id = 123;
@@ -1402,8 +1401,8 @@
 
   // By default, TemplateURLRef should not consider itself prepopulated.
   // Therefore we should not replace the unknown parameter.
-  EXPECT_FALSE(url.url_ref().ParseParameter(0, 10, &parsed_url, &replacements));
-  EXPECT_EQ("{fhqwhgads}abc", parsed_url);
+  EXPECT_TRUE(url.url_ref().ParseParameter(0, 10, &parsed_url, &replacements));
+  EXPECT_EQ("abc", parsed_url);
   EXPECT_TRUE(replacements.empty());
 
   // If the TemplateURLRef is prepopulated, we should remove unknown parameters.
@@ -1443,7 +1442,7 @@
   TemplateURLRef::Replacements replacements;
   bool valid = false;
 
-  EXPECT_EQ("{}", url.url_ref().ParseURL("{}", &replacements, nullptr, &valid));
+  EXPECT_EQ("", url.url_ref().ParseURL("{}", &replacements, nullptr, &valid));
   EXPECT_TRUE(valid);
   EXPECT_TRUE(replacements.empty());
 }
@@ -1454,10 +1453,10 @@
   TemplateURL url(data);
   TemplateURLRef::Replacements replacements;
   bool valid = false;
-  EXPECT_EQ("{}{}", url.url_ref().ParseURL("{}{{searchTerms}}", &replacements,
-                                           nullptr, &valid));
+  EXPECT_EQ("{}", url.url_ref().ParseURL("{{searchTerms}}", &replacements,
+                                         nullptr, &valid));
   ASSERT_EQ(1U, replacements.size());
-  EXPECT_EQ(3U, replacements[0].index);
+  EXPECT_EQ(1U, replacements[0].index);
   EXPECT_EQ(TemplateURLRef::SEARCH_TERMS, replacements[0].type);
   EXPECT_TRUE(valid);
 }
diff --git a/components/services/app_service/public/cpp/app_registry_cache.h b/components/services/app_service/public/cpp/app_registry_cache.h
index e842df2..dba972be 100644
--- a/components/services/app_service/public/cpp/app_registry_cache.h
+++ b/components/services/app_service/public/cpp/app_registry_cache.h
@@ -5,7 +5,11 @@
 #ifndef COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_APP_REGISTRY_CACHE_H_
 #define COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_APP_REGISTRY_CACHE_H_
 
+#include <functional>
 #include <map>
+#include <optional>
+#include <string>
+#include <string_view>
 #include <utility>
 #include <vector>
 
@@ -17,6 +21,7 @@
 #include "base/observer_list_types.h"
 #include "base/sequence_checker.h"
 #include "components/account_id/account_id.h"
+#include "components/services/app_service/public/cpp/app.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/app_update.h"
 
@@ -264,7 +269,7 @@
   base::ObserverList<Observer> observers_;
 
   // Maps from app_id to the latest state: the "sum" of all previous deltas.
-  std::map<std::string, AppPtr> states_;
+  std::map<std::string, AppPtr, std::less<>> states_;
 
   // Track the deltas being processed or are about to be processed by OnApps.
   // They are separate to manage the "notification and merging might be delayed
@@ -282,7 +287,7 @@
   // Nested OnApps calls are expected to be rare (but still dealt with
   // sensibly). In the typical case, OnApps should call DoOnApps exactly once,
   // and deltas_pending_ will stay empty.
-  std::map<std::string, raw_ptr<App, CtnExperimental>> deltas_in_progress_;
+  std::map<std::string, raw_ptr<App, CtnExperimental>, std::less<>> deltas_in_progress_;
   std::vector<AppPtr> deltas_pending_;
 
   // Saves app types which will finish initialization, and OnAppTypeInitialized
diff --git a/components/services/app_service/public/cpp/app_storage/app_storage.cc b/components/services/app_service/public/cpp/app_storage/app_storage.cc
index 959a4f9b..dba7494 100644
--- a/components/services/app_service/public/cpp/app_storage/app_storage.cc
+++ b/components/services/app_service/public/cpp/app_storage/app_storage.cc
@@ -4,14 +4,17 @@
 
 #include "components/services/app_service/public/cpp/app_storage/app_storage.h"
 
+#include <functional>
 #include <map>
 #include <memory>
+#include <string>
 #include <utility>
 
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
 #include "base/location.h"
 #include "base/task/task_runner.h"
+#include "components/services/app_service/public/cpp/app.h"
 #include "components/services/app_service/public/cpp/app_storage/app_storage_file_handler.h"
 #include "components/services/app_service/public/cpp/app_update.h"
 #include "components/services/app_service/public/cpp/icon_effects.h"
@@ -138,7 +141,8 @@
   auto app = update.Delta()->Clone();
   CHECK(app);
 
-  std::map<std::string, AppPtr>& saved_app_info = app_registry_cache_->states_;
+  std::map<std::string, AppPtr, std::less<>>& saved_app_info =
+      app_registry_cache_->states_;
   auto it = saved_app_info.find(app->app_id);
 
   // If the app is removed, check whether the app status in `saved_app_info`.
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index 0fc8443..d2d8fd0 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -67,7 +67,7 @@
 
 BASE_FEATURE(kTemporalSkipOverlaysWithRootCopyOutputRequests,
              "TemporalSkipOverlaysWithRootCopyOutputRequests",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kUseMultipleOverlays,
              "UseMultipleOverlays",
diff --git a/components/viz/service/display/resolved_frame_data.cc b/components/viz/service/display/resolved_frame_data.cc
index 21a72ab..f60968e1 100644
--- a/components/viz/service/display/resolved_frame_data.cc
+++ b/components/viz/service/display/resolved_frame_data.cc
@@ -397,6 +397,15 @@
         auto& tag_data = offset_tag_data_[sqs->offset_tag];
         if (!tag_data.current_offset.IsZero()) {
           sqs->quad_to_target_transform.PostTranslate(tag_data.current_offset);
+
+          if (!sqs->mask_filter_info.IsEmpty()) {
+            // Slim compositor enforces that mask filter info isn't added on
+            // a fixed parent layer that has a child layer with offset tag, so
+            // we can assume the mask filter info should also be translated.
+            // See crbug.com/361804880 for details.
+            sqs->mask_filter_info.ApplyTransform(
+                gfx::Transform::MakeTranslation(tag_data.current_offset));
+          }
         }
       }
     }
diff --git a/components/viz/service/display/resolved_frame_data_unittest.cc b/components/viz/service/display/resolved_frame_data_unittest.cc
index 19bb65de..c2f832e 100644
--- a/components/viz/service/display/resolved_frame_data_unittest.cc
+++ b/components/viz/service/display/resolved_frame_data_unittest.cc
@@ -814,5 +814,57 @@
   }
 }
 
+TEST_F(ResolvedFrameDataTest, OffsetTagMaskFilterTranslated) {
+  auto tag_def = MakeOffsetTagDefinition();
+  auto offset_tag = tag_def.tag;
+
+  constexpr gfx::Rect quad_rect(20, 30, 10, 10);
+  constexpr gfx::Vector2dF offset(10, 10);
+
+  gfx::LinearGradient gradient;
+  gradient.AddStep(0.0f, 0);
+  gradient.AddStep(1.0f, 255);
+  gfx::MaskFilterInfo mask_info(gfx::RRectF(gfx::RectF(quad_rect)), gradient);
+
+  Surface* surface = nullptr;
+  {
+    // The same layer introduces both offset tag and mask filter.
+    auto frame =
+        CompositorFrameBuilder()
+            .AddRenderPass(RenderPassBuilder(kOutputRect)
+                               .SetDamageRect(kOutputRect)
+                               .AddSolidColorQuad(quad_rect, SkColors::kRed)
+                               .SetQuadMaskFilterInfo(mask_info)
+                               .SetQuadOffsetTag(offset_tag))
+            .AddOffsetTagDefinition(tag_def)
+            .Build();
+    surface = SubmitCompositorFrame(std::move(frame));
+  }
+  ResolvedFrameData resolved_frame(&resource_provider_, surface, 0u,
+                                   AggregatedRenderPassId());
+
+  {
+    resolved_frame.UpdateForAggregation(render_pass_id_generator_);
+    resolved_frame.UpdateOffsetTags(
+        [&offset](const OffsetTagDefinition&) { return offset; });
+
+    ASSERT_THAT(resolved_frame.GetResolvedPasses(), testing::SizeIs(1));
+    auto& resolved_render_pass =
+        resolved_frame.GetResolvedPasses()[0].render_pass();
+
+    auto translated_mask_info = mask_info;
+    translated_mask_info.ApplyTransform(
+        gfx::Transform::MakeTranslation(offset));
+
+    // Verify that the mask filter is translated.
+    EXPECT_THAT(resolved_render_pass.quad_list,
+                testing::ElementsAre(
+                    testing::AllOf(IsSolidColorQuad(SkColors::kRed),
+                                   HasMaskFilterInfo(translated_mask_info))));
+
+    resolved_frame.ResetAfterAggregation();
+  }
+}
+
 }  // namespace
 }  // namespace viz
diff --git a/components/viz/test/compositor_frame_helpers.cc b/components/viz/test/compositor_frame_helpers.cc
index cfb0368..99e9302 100644
--- a/components/viz/test/compositor_frame_helpers.cc
+++ b/components/viz/test/compositor_frame_helpers.cc
@@ -290,6 +290,13 @@
   return *this;
 }
 
+RenderPassBuilder& RenderPassBuilder::SetQuadMaskFilterInfo(
+    const gfx::MaskFilterInfo& mask_filter_info) {
+  auto* sqs = GetLastQuadSharedQuadState();
+  sqs->mask_filter_info = mask_filter_info;
+  return *this;
+}
+
 SharedQuadState* RenderPassBuilder::AppendDefaultSharedQuadState(
     const gfx::Rect rect,
     const gfx::Rect visible_rect) {
diff --git a/components/viz/test/compositor_frame_helpers.h b/components/viz/test/compositor_frame_helpers.h
index 0b2cf12..123966d 100644
--- a/components/viz/test/compositor_frame_helpers.h
+++ b/components/viz/test/compositor_frame_helpers.h
@@ -175,6 +175,10 @@
   // Sets SharedQuadState::offset_tag for the last quad.
   RenderPassBuilder& SetQuadOffsetTag(const OffsetTag& tag);
 
+  // Sets SharedQuadState::mask_filter_info for the last quad.
+  RenderPassBuilder& SetQuadMaskFilterInfo(
+      const gfx::MaskFilterInfo& mask_filter_info);
+
  private:
   // Appends and returns a new SharedQuadState for quad.
   SharedQuadState* AppendDefaultSharedQuadState(const gfx::Rect rect,
diff --git a/content/browser/interest_group/auction_process_manager_unittest.cc b/content/browser/interest_group/auction_process_manager_unittest.cc
index 507cee8..a9b6fe4 100644
--- a/content/browser/interest_group/auction_process_manager_unittest.cc
+++ b/content/browser/interest_group/auction_process_manager_unittest.cc
@@ -107,7 +107,8 @@
       const url::Origin& top_window_origin,
       auction_worklet::mojom::AuctionWorkletPermissionsPolicyStatePtr
           permissions_policy_state,
-      std::optional<uint16_t> experiment_id) override {
+      std::optional<uint16_t> experiment_id,
+      auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key) override {
     NOTREACHED_IN_MIGRATION();
   }
 
diff --git a/content/browser/interest_group/auction_worklet_manager.cc b/content/browser/interest_group/auction_worklet_manager.cc
index b95bb3bc..aaafbc8c 100644
--- a/content/browser/interest_group/auction_worklet_manager.cc
+++ b/content/browser/interest_group/auction_worklet_manager.cc
@@ -525,7 +525,7 @@
           worklet_info_.signals_url, worklet_manager_->top_window_origin(),
           GetAuctionWorkletPermissionsPolicyState(delegate->GetFrame(),
                                                   worklet_info_.script_url),
-          worklet_info_.experiment_group_id);
+          worklet_info_.experiment_group_id, /*public_key=*/nullptr);
       seller_worklet_.set_disconnect_with_reason_handler(base::BindOnce(
           &WorkletOwner::OnWorkletDisconnected, base::Unretained(this)));
       break;
diff --git a/content/browser/interest_group/auction_worklet_manager_unittest.cc b/content/browser/interest_group/auction_worklet_manager_unittest.cc
index ff85ea7..178bb2e 100644
--- a/content/browser/interest_group/auction_worklet_manager_unittest.cc
+++ b/content/browser/interest_group/auction_worklet_manager_unittest.cc
@@ -647,7 +647,8 @@
       const url::Origin& top_window_origin,
       auction_worklet::mojom::AuctionWorkletPermissionsPolicyStatePtr
           permissions_policy_state,
-      std::optional<uint16_t> experiment_group_id) override {
+      std::optional<uint16_t> experiment_group_id,
+      auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key) override {
     DCHECK(!seller_worklet_);
 
     // Make sure this request came over the right pipe.
diff --git a/content/browser/interest_group/mock_auction_process_manager.cc b/content/browser/interest_group/mock_auction_process_manager.cc
index e85f53c..c443cb3 100644
--- a/content/browser/interest_group/mock_auction_process_manager.cc
+++ b/content/browser/interest_group/mock_auction_process_manager.cc
@@ -641,7 +641,8 @@
     const url::Origin& top_window_origin,
     auction_worklet::mojom::AuctionWorkletPermissionsPolicyStatePtr
         permissions_policy_state,
-    std::optional<uint16_t> experiment_group_id) {
+    std::optional<uint16_t> experiment_group_id,
+    auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key) {
   EXPECT_EQ(0u, seller_worklets_.count(script_source_url));
 
   // Make sure this request came over the right pipe.
diff --git a/content/browser/interest_group/mock_auction_process_manager.h b/content/browser/interest_group/mock_auction_process_manager.h
index f1c43b4..37c7252 100644
--- a/content/browser/interest_group/mock_auction_process_manager.h
+++ b/content/browser/interest_group/mock_auction_process_manager.h
@@ -471,7 +471,8 @@
       const url::Origin& top_window_origin,
       auction_worklet::mojom::AuctionWorkletPermissionsPolicyStatePtr
           permissions_policy_state,
-      std::optional<uint16_t> experiment_group_id) override;
+      std::optional<uint16_t> experiment_group_id,
+      auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key) override;
 
   // Set the expected timeout for an interest group with the specified name,
   // when it's received by a bidder worklet's FinishGenerateBid() method. Must
diff --git a/content/browser/web_contents/web_drag_dest_mac.mm b/content/browser/web_contents/web_drag_dest_mac.mm
index 79e72e0..ec84288 100644
--- a/content/browser/web_contents/web_drag_dest_mac.mm
+++ b/content/browser/web_contents/web_drag_dest_mac.mm
@@ -14,6 +14,7 @@
 
 #include <optional>
 
+#include "base/apple/foundation_util.h"
 #include "base/containers/span.h"
 #include "base/memory/raw_ptr.h"
 #include "base/ranges/algorithm.h"
@@ -483,8 +484,7 @@
         [pboard dataForType:ui::kUTTypeChromiumDataTransferCustomData];
     if (std::optional<std::unordered_map<std::u16string, std::u16string>>
             maybe_custom_data = ui::ReadCustomDataIntoMap(
-                base::span(reinterpret_cast<const uint8_t*>([customData bytes]),
-                           [customData length]));
+                base::apple::NSDataToSpan(customData));
         maybe_custom_data) {
       drop_data.custom_data = std::move(*maybe_custom_data);
     }
diff --git a/content/gpu/gpu_main.cc b/content/gpu/gpu_main.cc
index d2be94d0..59ab3b7 100644
--- a/content/gpu/gpu_main.cc
+++ b/content/gpu/gpu_main.cc
@@ -267,9 +267,17 @@
   std::unique_ptr<base::SingleThreadTaskExecutor> main_thread_task_executor;
   std::unique_ptr<ui::PlatformEventSource> event_source;
   if (command_line.HasSwitch(switches::kHeadless)) {
+#if BUILDFLAG(IS_MAC)
+    // CADisplayLink (Mac HW VSync) callback only works with NS_RUNLOOP.
+    main_thread_task_executor =
+        std::make_unique<base::SingleThreadTaskExecutor>(
+            base::MessagePumpType::NS_RUNLOOP);
+    main_thread_task_executor->SetWorkBatchSize(2);
+#else
     main_thread_task_executor =
         std::make_unique<base::SingleThreadTaskExecutor>(
             base::MessagePumpType::DEFAULT);
+#endif
   } else {
 #if BUILDFLAG(IS_WIN)
     // The GpuMain thread should not be pumping Windows messages because no UI
@@ -291,6 +299,8 @@
     // Cross-process CoreAnimation requires a CFRunLoop to function at all, and
     // requires a NSRunLoop to not starve under heavy load. See:
     // https://crbug.com/312462#c51 and https://crbug.com/783298
+    // CADisplayLink (Mac HW VSync) callback only works with NS_RUNLOOP. DEFAULT
+    // type does not support NSObject.
     main_thread_task_executor =
         std::make_unique<base::SingleThreadTaskExecutor>(
             base::MessagePumpType::NS_RUNLOOP);
diff --git a/content/services/auction_worklet/auction_worklet_service_impl.cc b/content/services/auction_worklet/auction_worklet_service_impl.cc
index 161c8d1d..1801ea1b 100644
--- a/content/services/auction_worklet/auction_worklet_service_impl.cc
+++ b/content/services/auction_worklet/auction_worklet_service_impl.cc
@@ -303,7 +303,8 @@
     const std::optional<GURL>& trusted_scoring_signals_url,
     const url::Origin& top_window_origin,
     mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state,
-    std::optional<uint16_t> experiment_group_id) {
+    std::optional<uint16_t> experiment_group_id,
+    mojom::TrustedSignalsPublicKeyPtr public_key) {
   std::vector<scoped_refptr<AuctionV8Helper>> v8_helpers;
   for (size_t i = 0; i < auction_seller_v8_helper_holders_.size(); ++i) {
     v8_helpers.push_back(auction_seller_v8_helper_holders_[i]->V8Helper());
@@ -317,6 +318,7 @@
       std::move(auction_network_events_handler), decision_logic_url,
       trusted_scoring_signals_url, top_window_origin,
       std::move(permissions_policy_state), experiment_group_id,
+      std::move(public_key),
       base::BindRepeating(
           &AuctionWorkletServiceImpl::GetNextSellerWorkletThreadIndex,
           base::Unretained(this)));
diff --git a/content/services/auction_worklet/auction_worklet_service_impl.h b/content/services/auction_worklet/auction_worklet_service_impl.h
index f2aea0c3..c3d09171 100644
--- a/content/services/auction_worklet/auction_worklet_service_impl.h
+++ b/content/services/auction_worklet/auction_worklet_service_impl.h
@@ -88,7 +88,8 @@
       const std::optional<GURL>& trusted_scoring_signals_url,
       const url::Origin& top_window_origin,
       mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state,
-      std::optional<uint16_t> experiment_group_id) override;
+      std::optional<uint16_t> experiment_group_id,
+      mojom::TrustedSignalsPublicKeyPtr public_key) override;
 
   // Returns an index in the seller thread pool, where the corresponding V8
   // thread will be used to execute the next task.
diff --git a/content/services/auction_worklet/public/mojom/auction_worklet_service.mojom b/content/services/auction_worklet/public/mojom/auction_worklet_service.mojom
index f497e716..d3a002c 100644
--- a/content/services/auction_worklet/public/mojom/auction_worklet_service.mojom
+++ b/content/services/auction_worklet/public/mojom/auction_worklet_service.mojom
@@ -172,5 +172,6 @@
       url.mojom.Url? trusted_scoring_signals_url,
       url.mojom.Origin top_window_origin,
       AuctionWorkletPermissionsPolicyState permissions_policy_state,
-      uint16? experiment_group_id);
+      uint16? experiment_group_id,
+      TrustedSignalsPublicKey? public_key);
 };
diff --git a/content/services/auction_worklet/seller_worklet.cc b/content/services/auction_worklet/seller_worklet.cc
index 24ebb1767..efe3f08 100644
--- a/content/services/auction_worklet/seller_worklet.cc
+++ b/content/services/auction_worklet/seller_worklet.cc
@@ -432,6 +432,7 @@
     const url::Origin& top_window_origin,
     mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state,
     std::optional<uint16_t> experiment_group_id,
+    mojom::TrustedSignalsPublicKeyPtr public_key,
     GetNextThreadIndexCallback get_next_thread_index_callback)
     : url_loader_factory_(std::move(pending_url_loader_factory)),
       script_source_url_(decision_logic_url),
@@ -475,7 +476,7 @@
                  *trusted_scoring_signals_url,
                  /*experiment_group_id=*/experiment_group_id,
                  /*trusted_bidding_signals_slot_size_param=*/std::string(),
-                 /*public_key=*/nullptr,
+                 std::move(public_key),
                  v8_helpers_[get_next_thread_index_callback_.Run()].get())
            : nullptr);
   trusted_signals_relation_ = ClassifyTrustedSignals(
@@ -553,6 +554,7 @@
   score_ad_task->component_expect_bid_currency = component_expect_bid_currency;
   score_ad_task->browser_signal_interest_group_owner =
       browser_signal_interest_group_owner;
+  score_ad_task->bidder_joining_origin = bidder_joining_origin;
   score_ad_task->browser_signal_render_url = browser_signal_render_url;
   score_ad_task->browser_signal_selected_buyer_and_seller_reporting_id =
       browser_signal_selected_buyer_and_seller_reporting_id;
@@ -2012,14 +2014,29 @@
             SignalsOriginRelation::kSameOriginSignals ||
         trusted_signals_relation_ ==
             SignalsOriginRelation::kPermittedCrossOriginSignals);
-  score_ad_task->trusted_scoring_signals_request =
-      trusted_signals_request_manager_->RequestScoringSignals(
-          score_ad_task->browser_signal_render_url,
-          score_ad_task->browser_signal_ad_components,
-          score_ad_task->auction_ad_config_non_shared_params
-              .max_trusted_scoring_signals_url_length,
-          base::BindOnce(&SellerWorklet::OnTrustedScoringSignalsDownloaded,
-                         base::Unretained(this), score_ad_task));
+
+  if (trusted_signals_request_manager_->HasPublicKey()) {
+    DCHECK(base::FeatureList::IsEnabled(
+        blink::features::kFledgeTrustedSignalsKVv2Support));
+
+    score_ad_task->trusted_scoring_signals_request =
+        trusted_signals_request_manager_->RequestKVv2ScoringSignals(
+            score_ad_task->browser_signal_render_url,
+            score_ad_task->browser_signal_ad_components,
+            score_ad_task->browser_signal_interest_group_owner,
+            score_ad_task->bidder_joining_origin,
+            base::BindOnce(&SellerWorklet::OnTrustedScoringSignalsDownloaded,
+                           base::Unretained(this), score_ad_task));
+  } else {
+    score_ad_task->trusted_scoring_signals_request =
+        trusted_signals_request_manager_->RequestScoringSignals(
+            score_ad_task->browser_signal_render_url,
+            score_ad_task->browser_signal_ad_components,
+            score_ad_task->auction_ad_config_non_shared_params
+                .max_trusted_scoring_signals_url_length,
+            base::BindOnce(&SellerWorklet::OnTrustedScoringSignalsDownloaded,
+                           base::Unretained(this), score_ad_task));
+  }
 }
 
 void SellerWorklet::OnTrustedScoringSignalsDownloaded(
diff --git a/content/services/auction_worklet/seller_worklet.h b/content/services/auction_worklet/seller_worklet.h
index 11f4aba..edccbd1 100644
--- a/content/services/auction_worklet/seller_worklet.h
+++ b/content/services/auction_worklet/seller_worklet.h
@@ -101,6 +101,7 @@
       const url::Origin& top_window_origin,
       mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state,
       std::optional<uint16_t> experiment_group_id,
+      mojom::TrustedSignalsPublicKeyPtr public_key,
       GetNextThreadIndexCallback next_thread_index_callback);
 
   explicit SellerWorklet(const SellerWorklet&) = delete;
@@ -201,6 +202,7 @@
     mojom::ComponentAuctionOtherSellerPtr browser_signals_other_seller;
     std::optional<blink::AdCurrency> component_expect_bid_currency;
     url::Origin browser_signal_interest_group_owner;
+    url::Origin bidder_joining_origin;
     GURL browser_signal_render_url;
     std::optional<std::string>
         browser_signal_selected_buyer_and_seller_reporting_id;
@@ -607,6 +609,7 @@
   mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;
 
   const GURL script_source_url_;
+  mojom::TrustedSignalsPublicKeyPtr public_key_;
 
   // Populated only if `this` was created with a non-null
   // `trusted_scoring_signals_url`.
diff --git a/content/services/auction_worklet/seller_worklet_unittest.cc b/content/services/auction_worklet/seller_worklet_unittest.cc
index b58dc03..a04cc59 100644
--- a/content/services/auction_worklet/seller_worklet_unittest.cc
+++ b/content/services/auction_worklet/seller_worklet_unittest.cc
@@ -30,9 +30,12 @@
 #include "base/test/values_test_util.h"
 #include "base/test/with_feature_override.h"
 #include "base/time/time.h"
+#include "components/cbor/writer.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
+#include "content/services/auction_worklet/public/cpp/cbor_test_util.h"
 #include "content/services/auction_worklet/public/cpp/real_time_reporting.h"
 #include "content/services/auction_worklet/public/mojom/auction_network_events_handler.mojom.h"
+#include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom-forward.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
 #include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
@@ -45,6 +48,7 @@
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "mojo/public/cpp/bindings/unique_receiver_set.h"
 #include "net/http/http_status_code.h"
+#include "net/third_party/quiche/src/quiche/oblivious_http/oblivious_http_gateway.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -81,6 +85,20 @@
   }
 )";
 
+const uint8_t kTestPrivateKey[] = {
+    0xff, 0x1f, 0x47, 0xb1, 0x68, 0xb6, 0xb9, 0xea, 0x65, 0xf7, 0x97,
+    0x4f, 0xf2, 0x2e, 0xf2, 0x36, 0x94, 0xe2, 0xf6, 0xb6, 0x8d, 0x66,
+    0xf3, 0xa7, 0x64, 0x14, 0x28, 0xd4, 0x45, 0x35, 0x01, 0x8f,
+};
+
+const uint8_t kTestPublicKey[] = {
+    0xa1, 0x5f, 0x40, 0x65, 0x86, 0xfa, 0xc4, 0x7b, 0x99, 0x59, 0x70,
+    0xf1, 0x85, 0xd9, 0xd8, 0x91, 0xc7, 0x4d, 0xcf, 0x1e, 0xb9, 0x1a,
+    0x7d, 0x50, 0xa5, 0x8b, 0x01, 0x68, 0x3e, 0x60, 0x05, 0x2d,
+};
+
+const uint8_t kKeyId = 0xFF;
+
 // Creates a seller script with scoreAd() returning the specified expression.
 // Allows using scoreAd() arguments, arbitrary values, incorrect types, etc.
 std::string CreateScoreAdScript(const std::string& raw_return_value,
@@ -263,6 +281,7 @@
             /*private_aggregation_allowed=*/true,
             /*shared_storage_allowed=*/false);
     experiment_group_id_ = std::nullopt;
+    public_key_ = nullptr;
     browser_signals_other_seller_.reset();
     component_expect_bid_currency_ = std::nullopt;
     browser_signal_interest_group_owner_ =
@@ -779,6 +798,7 @@
         auction_network_events_handler_.CreateRemote(), decision_logic_url_,
         trusted_scoring_signals_url_, top_window_origin_,
         permissions_policy_state_.Clone(), experiment_group_id_,
+        public_key_ ? public_key_.Clone() : nullptr,
         base::BindRepeating(&SellerWorkletTest::GetNextThreadIndex,
                             base::Unretained(this)));
 
@@ -866,6 +886,7 @@
   url::Origin top_window_origin_;
   mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state_;
   std::optional<uint16_t> experiment_group_id_;
+  mojom::TrustedSignalsPublicKeyPtr public_key_;
   mojom::ComponentAuctionOtherSellerPtr browser_signals_other_seller_;
   std::optional<blink::AdCurrency> component_expect_bid_currency_;
   url::Origin browser_signal_interest_group_owner_;
@@ -6120,6 +6141,171 @@
   }
 }
 
+class SellerWorkletKVv2Test : public SellerWorkletTest {
+ public:
+  SellerWorkletKVv2Test() {
+    scoped_feature_list_.InitWithFeatures(
+        {blink::features::kFledgeTrustedSignalsKVv2Support}, {});
+
+    public_key_ = mojom::TrustedSignalsPublicKey::New(
+        std::string(reinterpret_cast<const char*>(&kTestPublicKey[0]),
+                    sizeof(kTestPublicKey)),
+        kKeyId);
+  }
+
+ protected:
+  static std::string GenerateResponseBody(
+      const std::string& request_body,
+      const std::string& response_json_content) {
+    // Decrypt the request.
+    auto response_key_config = quiche::ObliviousHttpHeaderKeyConfig::Create(
+        kKeyId, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, EVP_HPKE_HKDF_SHA256,
+        EVP_HPKE_AES_256_GCM);
+    CHECK(response_key_config.ok()) << response_key_config.status();
+
+    auto ohttp_gateway =
+        quiche::ObliviousHttpGateway::Create(
+            std::string(reinterpret_cast<const char*>(&kTestPrivateKey[0]),
+                        sizeof(kTestPrivateKey)),
+            response_key_config.value())
+            .value();
+
+    auto received_request = ohttp_gateway.DecryptObliviousHttpRequest(
+        request_body, kTrustedSignalsKVv2EncryptionRequestMediaType);
+    CHECK(received_request.ok()) << received_request.status();
+
+    // Build response body.
+    cbor::Value::MapValue compression_group;
+    compression_group.try_emplace(cbor::Value("compressionGroupId"),
+                                  cbor::Value(0));
+    compression_group.try_emplace(cbor::Value("ttlMs"), cbor::Value(100));
+    compression_group.try_emplace(
+        cbor::Value("content"),
+        cbor::Value(test::ToCborVector(response_json_content)));
+
+    cbor::Value::ArrayValue compression_groups;
+    compression_groups.emplace_back(std::move(compression_group));
+
+    cbor::Value::MapValue body_map;
+    body_map.try_emplace(cbor::Value("compressionGroups"),
+                         cbor::Value(std::move(compression_groups)));
+
+    cbor::Value body_value(std::move(body_map));
+    std::optional<std::vector<uint8_t>> maybe_body_bytes =
+        cbor::Writer::Write(body_value);
+    CHECK(maybe_body_bytes);
+
+    std::string response_body = test::CreateKVv2ResponseBody(
+        base::as_string_view(maybe_body_bytes.value()));
+    auto response_context =
+        std::move(received_request).value().ReleaseContext();
+
+    // Encrypt the response body.
+    auto maybe_response = ohttp_gateway.CreateObliviousHttpResponse(
+        response_body, response_context,
+        kTrustedSignalsKVv2EncryptionResponseMediaType);
+    CHECK(maybe_response.ok()) << maybe_response.status();
+
+    return maybe_response->EncapsulateAndSerialize();
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(SellerWorkletKVv2Test, ScoreAdTrustedScoringSignals) {
+  const char kJson[] =
+      R"([{
+          "id": 0,
+          "dataVersion": 101,
+          "keyGroupOutputs": [
+            {
+              "tags": [
+                "renderUrls"
+              ],
+              "keyValues": {
+                "https://bar.test/": {
+                  "value": "1"
+                }
+              }
+            },
+            {
+              "tags": [
+                "adComponentRenderUrls"
+              ],
+              "keyValues": {
+                "https://barsub.test/": {
+                  "value": "2"
+                },
+                "https://foosub.test/": {
+                  "value": "[3]"
+                }
+              }
+            }
+          ]
+        }])";
+
+  const char kValidate[] = R"(
+    const expected = '{"renderURL":{"https://bar.test/":1},' +
+    '"renderUrl":{"https://bar.test/":1},"adComponentRenderURLs":' +
+    '{"https://barsub.test/":2,"https://foosub.test/":[3]},' +
+    '"adComponentRenderUrls":{"https://barsub.test/":2,' +
+    '"https://foosub.test/":[3]}}'
+    const actual = JSON.stringify(trustedScoringSignals);
+    if (actual === expected)
+      return 2;
+    throw actual + "!" + expected;
+  )";
+
+  const base::TimeDelta kDelay = base::Milliseconds(135);
+  trusted_scoring_signals_url_ =
+      GURL("https://url.test/trusted_scoring_signals");
+  browser_signal_render_url_ = GURL("https://bar.test/");
+  browser_signal_ad_components_ = {GURL("https://barsub.test/"),
+                                   GURL("https://foosub.test/")};
+
+  AddJavascriptResponse(
+      &url_loader_factory_, decision_logic_url_,
+      CreateScoreAdScript(/*raw_return_value=*/"1", kValidate));
+  auto seller_worklet = CreateWorklet();
+  ASSERT_TRUE(seller_worklet);
+  base::RunLoop run_loop;
+  RunScoreAdOnWorkletAsync(
+      seller_worklet.get(), /*expected_score=*/2,
+      /*expected_errors=*/{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
+      /*expected_data_version=*/101,
+      /*expected_debug_loss_report_url=*/std::nullopt,
+      /*expected_debug_win_report_url=*/std::nullopt,
+      mojom::RejectReason::kNotAvailable,
+      /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
+      /*expected_bid_in_seller_currency=*/std::nullopt,
+      /*expected_score_ad_timeout=*/false,
+      /*expected_signals_fetch_latency=*/kDelay,
+      /*expected_code_ready_latency=*/std::nullopt, run_loop.QuitClosure());
+  task_environment_.FastForwardBy(kDelay);
+
+  // Decrypt request and encrypt response.
+  ASSERT_EQ(1, url_loader_factory_.NumPending());
+  const network::ResourceRequest* pending_request;
+  ASSERT_TRUE(url_loader_factory_.IsPending(
+      trusted_scoring_signals_url_->spec(), &pending_request));
+
+  std::string request_body =
+      std::string(pending_request->request_body->elements()
+                      ->at(0)
+                      .As<network::DataElementBytes>()
+                      .AsStringPiece());
+  std::string response_body = GenerateResponseBody(request_body, kJson);
+  std::string headers =
+      base::StringPrintf("%s\nContent-Type: %s", kAllowFledgeHeader,
+                         "message/ad-auction-trusted-signals-request");
+  AddResponse(&url_loader_factory_, trusted_scoring_signals_url_.value(),
+              kAdAuctionTrustedSignalsMimeType,
+              /*charset=*/std::nullopt, response_body, headers);
+
+  run_loop.Run();
+}
+
 class SellerWorkletTwoThreadsSharedStorageAPIEnabledTest
     : public SellerWorkletSharedStorageAPIEnabledTest {
  public:
diff --git a/crypto/unexportable_key_mac.mm b/crypto/unexportable_key_mac.mm
index fef1b87..e4aab09 100644
--- a/crypto/unexportable_key_mac.mm
+++ b/crypto/unexportable_key_mac.mm
@@ -60,16 +60,10 @@
 // that shows this value. Therefore, it is left untranslated.
 constexpr char kAttrLabel[] = "Chromium unexportable key";
 
-// Returns a span of a CFDataRef.
-base::span<const uint8_t> ToSpan(CFDataRef data) {
-  return base::make_span(CFDataGetBytePtr(data),
-                         base::checked_cast<size_t>(CFDataGetLength(data)));
-}
-
 // Copies a CFDataRef into a vector of bytes.
 std::vector<uint8_t> CFDataToVec(CFDataRef data) {
-  base::span<const uint8_t> span = ToSpan(data);
-  return std::vector<uint8_t>(span.begin(), span.end());
+  auto span = base::apple::CFDataToSpan(data);
+  return {span.begin(), span.end()};
 }
 
 std::optional<std::vector<uint8_t>> Convertx963ToDerSpki(
@@ -119,7 +113,8 @@
         AppleKeychainV2::GetInstance().KeyCopyExternalRepresentation(
             public_key.get(), /*error=*/nil));
     CHECK(x962_bytes);
-    base::span<const uint8_t> x962_span = ToSpan(x962_bytes.get());
+    base::span<const uint8_t> x962_span =
+        base::apple::CFDataToSpan(x962_bytes.get());
     public_key_spki_ = *Convertx963ToDerSpki(x962_span);
   }
 
diff --git a/device/fido/enclave/icloud_recovery_key_mac.mm b/device/fido/enclave/icloud_recovery_key_mac.mm
index 815cf28..05dfed2 100644
--- a/device/fido/enclave/icloud_recovery_key_mac.mm
+++ b/device/fido/enclave/icloud_recovery_key_mac.mm
@@ -44,12 +44,6 @@
 // access group is not set.
 static const uint kSecAttrTypeFolsom = 'flsm';
 
-// Returns a span of a CFDataRef.
-base::span<const uint8_t> ToSpan(CFDataRef data) {
-  return base::make_span(CFDataGetBytePtr(data),
-                         base::checked_cast<size_t>(CFDataGetLength(data)));
-}
-
 // Returns the public key in uncompressed x9.62 format encoded in padded base64.
 NSString* EncodePublicKey(const trusted_vault::SecureBoxPublicKey& public_key) {
   return base::SysUTF8ToNSString(
@@ -174,7 +168,8 @@
     CFDataRef key = base::apple::CFCastStrict<CFDataRef>(
         CFDictionaryGetValue(item, kSecValueData));
     std::unique_ptr<trusted_vault::SecureBoxKeyPair> key_pair =
-        trusted_vault::SecureBoxKeyPair::CreateByPrivateKeyImport(ToSpan(key));
+        trusted_vault::SecureBoxKeyPair::CreateByPrivateKeyImport(
+            base::apple::CFDataToSpan(key));
     if (!key_pair) {
       FIDO_LOG(ERROR) << "iCloud recovery key is corrupted, skipping";
       continue;
diff --git a/device/fido/mac/credential_store.mm b/device/fido/mac/credential_store.mm
index 37d223b5..d66cba7 100644
--- a/device/fido/mac/credential_store.mm
+++ b/device/fido/mac/credential_store.mm
@@ -453,9 +453,7 @@
       DLOG(ERROR) << "missing application label";
       continue;
     }
-    if (!DeleteCredentialById(base::make_span(
-            CFDataGetBytePtr(credential_id_data),
-            base::checked_cast<size_t>(CFDataGetLength(credential_id_data))))) {
+    if (!DeleteCredentialById(base::apple::CFDataToSpan(credential_id_data))) {
       // Indicate failure, but keep deleting remaining items.
       result = false;
     }
@@ -565,9 +563,9 @@
       FIDO_LOG(ERROR) << "credential with missing application label";
       return std::nullopt;
     }
-    std::vector<uint8_t> credential_id(CFDataGetBytePtr(application_label),
-                                       CFDataGetBytePtr(application_label) +
-                                           CFDataGetLength(application_label));
+    auto credential_id_span = base::apple::CFDataToSpan(application_label);
+    const std::vector<uint8_t> credential_id(credential_id_span.begin(),
+                                             credential_id_span.end());
     if (!credential_ids.empty() &&
         !base::Contains(credential_ids, credential_id)) {
       continue;
@@ -582,10 +580,7 @@
     // On version < 3 credentials, kSecAttrApplicationTag is a CFStringRef,
     // which means `application_tag_ref` would be nullptr.
     if (application_tag_ref) {
-      const base::span<const uint8_t> application_tag(
-          CFDataGetBytePtr(application_tag_ref),
-          CFDataGetBytePtr(application_tag_ref) +
-              CFDataGetLength(application_tag_ref));
+      auto application_tag = base::apple::CFDataToSpan(application_tag_ref);
       metadata = UnsealMetadataFromApplicationTag(config_.metadata_secret,
                                                   rp_id_value, application_tag);
     } else {
diff --git a/device/fido/mac/util.mm b/device/fido/mac/util.mm
index e67387cb..6a88eed 100644
--- a/device/fido/mac/util.mm
+++ b/device/fido/mac/util.mm
@@ -137,9 +137,8 @@
     LOG(ERROR) << "SecKeyCreateSignature failed: " << err.get();
     return std::nullopt;
   }
-  return std::vector<uint8_t>(
-      CFDataGetBytePtr(sig_data.get()),
-      CFDataGetBytePtr(sig_data.get()) + CFDataGetLength(sig_data.get()));
+  auto sig_data_span = base::apple::CFDataToSpan(sig_data.get());
+  return std::vector<uint8_t>(sig_data_span.begin(), sig_data_span.end());
 }
 
 // SecKeyRefToECPublicKey converts a SecKeyRef for a public key into an
@@ -154,9 +153,7 @@
     LOG(ERROR) << "SecCopyExternalRepresentation failed: " << err.get();
     return nullptr;
   }
-  base::span<const uint8_t> key_data = base::make_span(
-      CFDataGetBytePtr(data_ref.get()),
-      base::checked_cast<size_t>(CFDataGetLength(data_ref.get())));
+  auto key_data = base::apple::CFDataToSpan(data_ref.get());
   auto key = P256PublicKey::ParseX962Uncompressed(
       static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), key_data);
   if (!key) {
diff --git a/docs/ios/build_instructions.md b/docs/ios/build_instructions.md
index 27a984bf..c58f6ed 100644
--- a/docs/ios/build_instructions.md
+++ b/docs/ios/build_instructions.md
@@ -13,7 +13,7 @@
 ## System requirements
 
 * A 64-bit Mac capable of running the required version of Xcode.
-* [Xcode](https://developer.apple.com/xcode) 15.0 or higher.
+* [Xcode](https://developer.apple.com/xcode) 16.0 or higher.
 
 Note: after installing Xcode, you need to launch it and to let it install
 the iOS simulator. This is required as part of the build, see [this discussion](
diff --git a/docs/updater/functional_spec.md b/docs/updater/functional_spec.md
index 8751ded..de990c88 100644
--- a/docs/updater/functional_spec.md
+++ b/docs/updater/functional_spec.md
@@ -481,6 +481,10 @@
 briefly before a full-fledged UI is shown. Installer initialization involves
 unzipping and unpacking the installer files.
 
+The splash screen logo can be customized by editing
+[logo.bmp](https://source.chromium.org/chromium/chromium/src/+/main:chrome/updater/win/installer/logo.bmp)
+.
+
 During installation, the user is presented with a UI that displays the progress
 of the download and installation. The user may close the dialog, which cancels
 the installation. A cancelled installation still results in an event ping to
diff --git a/extensions/common/mojom/api_permission_id.mojom b/extensions/common/mojom/api_permission_id.mojom
index 3bb26a7..368cf91 100644
--- a/extensions/common/mojom/api_permission_id.mojom
+++ b/extensions/common/mojom/api_permission_id.mojom
@@ -284,6 +284,7 @@
   kChromeOSManagementAudio = 257,
   kChromeOSDiagnosticsNetworkInfoForMlab = 258,
   kAIAssistantOriginTrial = 259,
+  kExperimentalAiData = 260,
 
   // Add new entries at the end of the enum and be sure to update the
   // "ExtensionPermission3" enum in
diff --git a/fuchsia_web/webengine/browser/web_engine_config.cc b/fuchsia_web/webengine/browser/web_engine_config.cc
index e7ad1a0..5d065a6 100644
--- a/fuchsia_web/webengine/browser/web_engine_config.cc
+++ b/fuchsia_web/webengine/browser/web_engine_config.cc
@@ -12,6 +12,7 @@
 #include "base/logging.h"
 #include "base/metrics/field_trial.h"
 #include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "cc/base/switches.h"
@@ -51,14 +52,15 @@
 // `value`, otherwise the switch will be set to `value`.
 void AppendToSwitch(std::string_view switch_name,
                     std::string_view value,
-                    base::CommandLine* command_line) {
+                    base::CommandLine* command_line,
+                    std::string_view separator = ",") {
   if (!command_line->HasSwitch(switch_name)) {
     command_line->AppendSwitchNative(switch_name, value);
     return;
   }
 
   std::string new_value = base::StrCat(
-      {command_line->GetSwitchValueASCII(switch_name), ",", value});
+      {command_line->GetSwitchValueASCII(switch_name), separator, value});
   command_line->RemoveSwitch(switch_name);
   command_line->AppendSwitchNative(switch_name, new_value);
 }
@@ -193,5 +195,23 @@
   command_line->AppendSwitchASCII(switches::kEnableHardwareOverlays,
                                   "underlay");
 
+  std::optional<int> max_old_space =
+      config.FindInt("js-heap-max-old-space-size");
+  if (max_old_space) {
+    AppendToSwitch(
+        blink::switches::kJavaScriptFlags,
+        "--max_old_space_size=" + base::NumberToString(max_old_space.value()),
+        command_line, " ");
+  }
+
+  std::optional<int> max_semi_space =
+      config.FindInt("js-heap-max-semi-space-size");
+  if (max_semi_space) {
+    AppendToSwitch(
+        blink::switches::kJavaScriptFlags,
+        "--max_semi_space_size=" + base::NumberToString(max_semi_space.value()),
+        command_line, " ");
+  }
+
   return true;
 }
diff --git a/headless/README.md b/headless/README.md
index 9e2f8cb..2253e8be 100644
--- a/headless/README.md
+++ b/headless/README.md
@@ -5,123 +5,77 @@
 DOM) and generating bitmaps from page contents -- using all the modern web
 platform features provided by Chromium and Blink.
 
-There are two ways to use Headless Chromium:
-
-## Usage via the DevTools remote debugging protocol
-
-1. Start a normal Chrome binary with the `--headless` command line flag:
-
-```sh
-$ chrome --headless --remote-debugging-port=9222 https://chromium.org/
-```
-
-2. Navigate to `http://localhost:9222/` in another browser to open the
-[DevTools](https://developer.chrome.com/devtools) interface or use a tool such
-as [Selenium](http://www.seleniumhq.org/) to drive the headless browser.
-
-## Usage from Node.js
-
-For example, the [chrome-remote-interface](https://github.com/cyrus-and/chrome-remote-interface)
-Node.js package can be used to extract a page's DOM like this:
-
-```js
-const CDP = require('chrome-remote-interface');
-
-CDP((client) => {
-  // Extract used DevTools domains.
-  const {Page, Runtime} = client;
-
-  // Enable events on domains we are interested in.
-  Promise.all([
-    Page.enable()
-  ]).then(() => {
-    return Page.navigate({url: 'https://example.com'});
-  });
-
-  // Evaluate outerHTML after page has loaded.
-  Page.loadEventFired(() => {
-    Runtime.evaluate({expression: 'document.body.outerHTML'}).then((result) => {
-      console.log(result.result.value);
-      client.close();
-    });
-  });
-}).on('error', (err) => {
-  console.error('Cannot connect to browser:', err);
-});
-```
-
-## Usage as a C++ library
-
-Headless Chromium can be built as a library for embedding into a C++
-application. This approach is otherwise similar to controlling the browser over
-a DevTools connection, but it provides more customization points, e.g., for
-networking and [mojo services](https://docs.google.com/document/d/1Fr6_DJH6OK9rG3-ibMvRPTNnHsAXPk0VzxxiuJDSK3M/edit#heading=h.qh0udvlk963d).
-
-[Headless Example](https://cs.chromium.org/chromium/src/headless/app/headless_example.cc)
-is a small sample application which demonstrates the use of the headless C++
-API. It loads a web page and outputs the resulting DOM. To run it, first
-initialize a headless build configuration:
-
-```sh
-$ mkdir -p out/Debug
-$ echo 'import("//build/args/headless.gn")' > out/Debug/args.gn
-$ gn gen out/Debug
-```
-
-Then build the example:
-
-```sh
-$ ninja -C out/Debug headless_example
-```
-
-After the build completes, the example can be run with the following command:
-
-```sh
-$ out/Debug/headless_example https://www.google.com/
-```
-
-[Headless Shell](https://cs.chromium.org/chromium/src/headless/app/headless_shell.cc)
-is a more capable headless application. For instance, it supports remote
-debugging with the [DevTools](https://developer.chrome.com/devtools) protocol.
-To do this, start the application with an argument specifying the debugging
-port:
-
-```sh
-$ ninja -C out/Debug headless_shell
-$ out/Debug/headless_shell --remote-debugging-port=9222 https://youtube.com/
-```
-
-Then navigate to `http://localhost:9222/` with your browser.
-
 As of M118, precompiled `headless_shell` binaries are available for download
 under the name `chrome-headless-shell` via [Chrome for Testing
 infrastructure](https://googlechromelabs.github.io/chrome-for-testing/).
 
-## Embedder API
+There are two ways to use Headless Chromium:
 
-The embedder API allows developers to integrate the headless library into their
-application. The API provides default implementations for low level adaptation
-points such as networking and the run loop.
+## Usage via the DevTools remote debugging protocol
 
-The main embedder API classes are:
+1. Start a normal Chrome binary with the `--headless=old` command line flag:
 
-- `HeadlessBrowser::Options::Builder` - Defines the embedding options, e.g.:
-  - `SetMessagePump` - Replaces the default base message pump. See
-    `base::MessagePump`.
-  - `SetProxyServer` - Configures an HTTP/HTTPS proxy server to be used for
-    accessing the network.
+```sh
+$ chrome --headless=old --remote-debugging-port=9222 https://chromium.org/
+```
 
-## Client/DevTools API
+2. Navigate to `chrome://inspect/` in another instance of Chrome.
 
-The headless client API is used to drive the browser and interact with loaded
-web pages. Its main classes are:
+## Usage from Node.js
 
-- `HeadlessBrowser` - Represents the global headless browser instance.
-- `HeadlessWebContents` - Represents a single "tab" within the browser.
-- `HeadlessDevToolsClient` - Provides a C++ interface for inspecting and
-  controlling a tab. The API functions corresponds to [DevTools commands](https://developer.chrome.com/devtools/docs/debugger-protocol).
-  See the [client API documentation](https://docs.google.com/document/d/1rlqcp8nk-ZQvldNJWdbaMbwfDbJoOXvahPCDoPGOwhQ/edit#)
-  for more information.
+For example, the [chrome-remote-interface](https://github.com/cyrus-and/chrome-remote-interface) Node.js package can be used to
+extract a page's DOM like this:
+
+```js
+const CDP = require('chrome-remote-interface');
+
+(async () => {
+  let client;
+  try {
+    // Connect to browser
+    client = await CDP();
+
+    // Extract used DevTools domains.
+    const {Page, Runtime} = client;
+
+    // Enable events on domains we are interested in.
+    await Page.enable();
+    await Page.navigate({url: 'https://example.com'});
+    await Page.loadEventFired();
+
+    // Evaluate outerHTML after page has loaded.
+    const expression = {expression: 'document.body.outerHTML'};
+    const { result } = await Runtime.evaluate(expression);
+    console.log(result.value);
+
+  } catch (err) {
+    console.error('Cannot connect to browser:', err);
+
+  } finally {
+    if (client) {
+      await client.close();
+    }
+  }
+})();
+```
+
+Alternatvely, the [Puppeteer](https://pptr.dev/guides/what-is-puppeteer) Node.js package can be used to communicate
+with headless, for example:
+```js
+import puppeteer from 'puppeteer';
+
+(async () => {
+  const browser = await puppeteer.launch({headless: 'shell'});
+
+  const page = await browser.newPage();
+  await page.goto('https://example.com');
+
+  const title = await page.evaluate(() => document.title);
+  console.log(title);
+
+  await browser.close();
+})();
+```
 
 ## Resources and Documentation
 
@@ -139,7 +93,6 @@
 * [Virtual Time in
   Blink](https://docs.google.com/document/d/1y9KDT_ZEzT7pBeY6uzVt1dgKlwc1OB_vY4NZO1zBQmo/edit?usp=sharing)
 * [Headless Chrome architecture (Design Doc)](https://docs.google.com/document/d/11zIkKkLBocofGgoTeeyibB2TZ_k7nR78v7kNelCatUE)
-* [Headless Chrome C++ DevTools API](https://docs.google.com/document/d/1rlqcp8nk-ZQvldNJWdbaMbwfDbJoOXvahPCDoPGOwhQ/edit#heading=h.ng2bxb15li9a)
 * [Session isolation in Headless Chrome](https://docs.google.com/document/d/1XAKvrxtSEoe65vNghSWC5S3kJ--z2Zpt2UWW1Fi8GiM/edit)
 * [Headless Chrome mojo service](https://docs.google.com/document/d/1Fr6_DJH6OK9rG3-ibMvRPTNnHsAXPk0VzxxiuJDSK3M/edit#heading=h.qh0udvlk963d)
 * [Controlling BeginFrame through DevTools](https://docs.google.com/document/d/1LVMYDkfjrrX9PNkrD8pJH5-Np_XUTQHIuJ8IEOirQH4/edit?ts=57d96dbd#heading=h.ndv831lc9uf0)
diff --git a/infra/config/generated/builders/ci/android-15-x64-fyi-rel/targets/chromium.android.fyi.json b/infra/config/generated/builders/ci/android-15-x64-fyi-rel/targets/chromium.android.fyi.json
index 1e0a69d9..2258f56 100644
--- a/infra/config/generated/builders/ci/android-15-x64-fyi-rel/targets/chromium.android.fyi.json
+++ b/infra/config/generated/builders/ci/android-15-x64-fyi-rel/targets/chromium.android.fyi.json
@@ -5,7 +5,7 @@
         "args": [
           "--emulator-debug-tags=all,-qemud,-sensors",
           "--avd-config=../../tools/android/avd/proto/android_35_google_apis_x64.textpb",
-          "--gtest_filter=-All/SharedStorageChromeBrowserTest.CrossOriginWorklet_SelectURL_Success/*",
+          "--gtest_filter=-All/SharedStorageChromeBrowserTest.CrossOriginWorklet_SelectURL_Success/*:PaymentHandler*",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
diff --git a/infra/config/generated/builders/ci/android-cast-arm-dbg/targets/chromium.android.json b/infra/config/generated/builders/ci/android-cast-arm-dbg/targets/chromium.android.json
index 7ba7a69a..b32468f 100644
--- a/infra/config/generated/builders/ci/android-cast-arm-dbg/targets/chromium.android.json
+++ b/infra/config/generated/builders/ci/android-cast-arm-dbg/targets/chromium.android.json
@@ -16,6 +16,39 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "cast_android_cma_backend_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "cast_android_cma_backend_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
+            "device_os_type": "userdebug",
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_android_cma_backend_unittests",
+        "test_id_prefix": "ninja://chromecast/media/cma/backend/android:cast_android_cma_backend_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "cast_audio_backend_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -27,10 +60,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -58,10 +93,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -89,10 +126,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -109,37 +148,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_display_settings_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_display_settings_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_display_settings_unittests",
-        "test_id_prefix": "ninja://chromecast/ui/display_settings:cast_display_settings_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_media_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -151,10 +159,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -171,37 +181,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_shell_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_shell_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_shell_unittests",
-        "test_id_prefix": "ninja://chromecast:cast_shell_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -213,10 +192,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
diff --git a/infra/config/generated/builders/ci/android-cast-arm-rel/targets/chromium.android.json b/infra/config/generated/builders/ci/android-cast-arm-rel/targets/chromium.android.json
index 3643e1c..0c39c9f 100644
--- a/infra/config/generated/builders/ci/android-cast-arm-rel/targets/chromium.android.json
+++ b/infra/config/generated/builders/ci/android-cast-arm-rel/targets/chromium.android.json
@@ -16,6 +16,39 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "cast_android_cma_backend_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "cast_android_cma_backend_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
+            "device_os_type": "userdebug",
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_android_cma_backend_unittests",
+        "test_id_prefix": "ninja://chromecast/media/cma/backend/android:cast_android_cma_backend_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "cast_audio_backend_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -27,10 +60,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -58,10 +93,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -89,10 +126,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -109,37 +148,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_display_settings_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_display_settings_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_display_settings_unittests",
-        "test_id_prefix": "ninja://chromecast/ui/display_settings:cast_display_settings_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_media_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -151,10 +159,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -171,37 +181,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_shell_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_shell_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_shell_unittests",
-        "test_id_prefix": "ninja://chromecast:cast_shell_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -213,10 +192,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
diff --git a/infra/config/generated/builders/ci/android-cast-arm64-dbg/targets/chromium.android.json b/infra/config/generated/builders/ci/android-cast-arm64-dbg/targets/chromium.android.json
index ab168a2d..b7244f0 100644
--- a/infra/config/generated/builders/ci/android-cast-arm64-dbg/targets/chromium.android.json
+++ b/infra/config/generated/builders/ci/android-cast-arm64-dbg/targets/chromium.android.json
@@ -16,6 +16,37 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "cast_android_cma_backend_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "cast_android_cma_backend_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "device_os": "AP1A.240505.005",
+            "device_os_type": "userdebug",
+            "device_type": "tangorpro",
+            "os": "Android"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_android_cma_backend_unittests",
+        "test_id_prefix": "ninja://chromecast/media/cma/backend/android:cast_android_cma_backend_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "cast_audio_backend_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -109,37 +140,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_display_settings_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_display_settings_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_display_settings_unittests",
-        "test_id_prefix": "ninja://chromecast/ui/display_settings:cast_display_settings_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_media_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -171,37 +171,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_shell_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_shell_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_shell_unittests",
-        "test_id_prefix": "ninja://chromecast:cast_shell_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
diff --git a/infra/config/generated/builders/ci/android-cast-arm64-rel/targets/chromium.android.json b/infra/config/generated/builders/ci/android-cast-arm64-rel/targets/chromium.android.json
index a7ea6d6..01bb54c 100644
--- a/infra/config/generated/builders/ci/android-cast-arm64-rel/targets/chromium.android.json
+++ b/infra/config/generated/builders/ci/android-cast-arm64-rel/targets/chromium.android.json
@@ -16,6 +16,37 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "cast_android_cma_backend_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "cast_android_cma_backend_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "device_os": "AP1A.240505.005",
+            "device_os_type": "userdebug",
+            "device_type": "tangorpro",
+            "os": "Android"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_android_cma_backend_unittests",
+        "test_id_prefix": "ninja://chromecast/media/cma/backend/android:cast_android_cma_backend_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "cast_audio_backend_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -109,37 +140,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_display_settings_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_display_settings_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_display_settings_unittests",
-        "test_id_prefix": "ninja://chromecast/ui/display_settings:cast_display_settings_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_media_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -171,37 +171,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_shell_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_shell_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_shell_unittests",
-        "test_id_prefix": "ninja://chromecast:cast_shell_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
diff --git a/infra/config/generated/builders/try/android-15-x64-fyi-rel/targets/chromium.android.fyi.json b/infra/config/generated/builders/try/android-15-x64-fyi-rel/targets/chromium.android.fyi.json
index 1e0a69d9..2258f56 100644
--- a/infra/config/generated/builders/try/android-15-x64-fyi-rel/targets/chromium.android.fyi.json
+++ b/infra/config/generated/builders/try/android-15-x64-fyi-rel/targets/chromium.android.fyi.json
@@ -5,7 +5,7 @@
         "args": [
           "--emulator-debug-tags=all,-qemud,-sensors",
           "--avd-config=../../tools/android/avd/proto/android_35_google_apis_x64.textpb",
-          "--gtest_filter=-All/SharedStorageChromeBrowserTest.CrossOriginWorklet_SelectURL_Success/*",
+          "--gtest_filter=-All/SharedStorageChromeBrowserTest.CrossOriginWorklet_SelectURL_Success/*:PaymentHandler*",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
diff --git a/infra/config/generated/builders/try/android-cast-arm-dbg/targets/chromium.android.json b/infra/config/generated/builders/try/android-cast-arm-dbg/targets/chromium.android.json
index 7ba7a69a..b32468f 100644
--- a/infra/config/generated/builders/try/android-cast-arm-dbg/targets/chromium.android.json
+++ b/infra/config/generated/builders/try/android-cast-arm-dbg/targets/chromium.android.json
@@ -16,6 +16,39 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "cast_android_cma_backend_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "cast_android_cma_backend_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
+            "device_os_type": "userdebug",
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_android_cma_backend_unittests",
+        "test_id_prefix": "ninja://chromecast/media/cma/backend/android:cast_android_cma_backend_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "cast_audio_backend_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -27,10 +60,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -58,10 +93,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -89,10 +126,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -109,37 +148,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_display_settings_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_display_settings_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_display_settings_unittests",
-        "test_id_prefix": "ninja://chromecast/ui/display_settings:cast_display_settings_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_media_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -151,10 +159,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -171,37 +181,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_shell_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_shell_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_shell_unittests",
-        "test_id_prefix": "ninja://chromecast:cast_shell_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -213,10 +192,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
diff --git a/infra/config/generated/builders/try/android-cast-arm-rel/targets/chromium.android.json b/infra/config/generated/builders/try/android-cast-arm-rel/targets/chromium.android.json
index 3643e1c..0c39c9f 100644
--- a/infra/config/generated/builders/try/android-cast-arm-rel/targets/chromium.android.json
+++ b/infra/config/generated/builders/try/android-cast-arm-rel/targets/chromium.android.json
@@ -16,6 +16,39 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "cast_android_cma_backend_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "cast_android_cma_backend_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
+            "device_os_type": "userdebug",
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_android_cma_backend_unittests",
+        "test_id_prefix": "ninja://chromecast/media/cma/backend/android:cast_android_cma_backend_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "cast_audio_backend_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -27,10 +60,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -58,10 +93,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -89,10 +126,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -109,37 +148,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_display_settings_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_display_settings_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_display_settings_unittests",
-        "test_id_prefix": "ninja://chromecast/ui/display_settings:cast_display_settings_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_media_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -151,10 +159,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -171,37 +181,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_shell_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_shell_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_shell_unittests",
-        "test_id_prefix": "ninja://chromecast:cast_shell_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -213,10 +192,12 @@
         },
         "swarming": {
           "dimensions": {
-            "device_os": "AP1A.240505.005",
+            "device_os": "PQ3A.190801.002",
+            "device_os_flavor": "google",
             "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
+            "device_type": "walleye",
+            "os": "Android",
+            "pool": "chromium.tests"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
diff --git a/infra/config/generated/builders/try/android-cast-arm64-dbg/targets/chromium.android.json b/infra/config/generated/builders/try/android-cast-arm64-dbg/targets/chromium.android.json
index ab168a2d..b7244f0 100644
--- a/infra/config/generated/builders/try/android-cast-arm64-dbg/targets/chromium.android.json
+++ b/infra/config/generated/builders/try/android-cast-arm64-dbg/targets/chromium.android.json
@@ -16,6 +16,37 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "cast_android_cma_backend_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "cast_android_cma_backend_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "device_os": "AP1A.240505.005",
+            "device_os_type": "userdebug",
+            "device_type": "tangorpro",
+            "os": "Android"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_android_cma_backend_unittests",
+        "test_id_prefix": "ninja://chromecast/media/cma/backend/android:cast_android_cma_backend_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "cast_audio_backend_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -109,37 +140,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_display_settings_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_display_settings_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_display_settings_unittests",
-        "test_id_prefix": "ninja://chromecast/ui/display_settings:cast_display_settings_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_media_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -171,37 +171,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_shell_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_shell_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_shell_unittests",
-        "test_id_prefix": "ninja://chromecast:cast_shell_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
diff --git a/infra/config/generated/builders/try/android-cast-arm64-rel/targets/chromium.android.json b/infra/config/generated/builders/try/android-cast-arm64-rel/targets/chromium.android.json
index a7ea6d6..01bb54c 100644
--- a/infra/config/generated/builders/try/android-cast-arm64-rel/targets/chromium.android.json
+++ b/infra/config/generated/builders/try/android-cast-arm64-rel/targets/chromium.android.json
@@ -16,6 +16,37 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "cast_android_cma_backend_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "cast_android_cma_backend_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "device_os": "AP1A.240505.005",
+            "device_os_type": "userdebug",
+            "device_type": "tangorpro",
+            "os": "Android"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_android_cma_backend_unittests",
+        "test_id_prefix": "ninja://chromecast/media/cma/backend/android:cast_android_cma_backend_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "cast_audio_backend_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -109,37 +140,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_display_settings_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_display_settings_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_display_settings_unittests",
-        "test_id_prefix": "ninja://chromecast/ui/display_settings:cast_display_settings_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_media_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -171,37 +171,6 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "cast_shell_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "cast_shell_unittests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "dimensions": {
-            "device_os": "AP1A.240505.005",
-            "device_os_type": "userdebug",
-            "device_type": "tangorpro",
-            "os": "Android"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_shell_unittests",
-        "test_id_prefix": "ninja://chromecast:cast_shell_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
             "cast_unittests"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
diff --git a/infra/config/generated/testing/gn_isolate_map.pyl b/infra/config/generated/testing/gn_isolate_map.pyl
index 23a702a..40cf95c3 100644
--- a/infra/config/generated/testing/gn_isolate_map.pyl
+++ b/infra/config/generated/testing/gn_isolate_map.pyl
@@ -241,6 +241,10 @@
       "--disable-extensions",
     ],
   },
+  "cast_android_cma_backend_unittests": {
+    "label": "//chromecast/media/cma/backend/android:cast_android_cma_backend_unittests",
+    "type": "console_test_launcher",
+  },
   "cast_audio_backend_unittests": {
     "label": "//chromecast/media/cma/backend:cast_audio_backend_unittests",
     "type": "console_test_launcher",
diff --git a/infra/config/lib/targets-internal/img/creation/targets-variant.png b/infra/config/lib/targets-internal/img/creation/targets-variant.png
index db6dbd70..8094abd 100644
--- a/infra/config/lib/targets-internal/img/creation/targets-variant.png
+++ b/infra/config/lib/targets-internal/img/creation/targets-variant.png
Binary files differ
diff --git a/infra/config/lib/targets.star b/infra/config/lib/targets.star
index cf7e32d..f4062a1 100644
--- a/infra/config/lib/targets.star
+++ b/infra/config/lib/targets.star
@@ -537,6 +537,9 @@
             identifies the variant of the test being run. When tests are
             expanded with the variant, this will be appended to the test
             name.
+        generate_pyl_entry: If true, the generated variants.pyl will
+            contain an entry allowing the mixin to be used by
+            generate_buildbot_json.py.
         enabled: Whether or not the variant is enabled. By default, a
             variant is enabled. If a variant is not enabled, then it
             will be ignored when expanding a test suite with variants.
diff --git a/infra/config/subprojects/chromium/ci/chromium.android.fyi.star b/infra/config/subprojects/chromium/ci/chromium.android.fyi.star
index c7b4b48..eae5c386 100644
--- a/infra/config/subprojects/chromium/ci/chromium.android.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.android.fyi.star
@@ -848,7 +848,9 @@
             "android_browsertests": targets.mixin(
                 args = [
                     # https://crbug.com/361042311
-                    "--gtest_filter=-All/SharedStorageChromeBrowserTest.CrossOriginWorklet_SelectURL_Success/*",
+                    ("--gtest_filter=-All/SharedStorageChromeBrowserTest.CrossOriginWorklet_SelectURL_Success/*:" +
+                     # https://crbug.com/345100678
+                     "PaymentHandler*"),
                 ],
             ),
             "content_browsertests": targets.mixin(
diff --git a/infra/config/subprojects/chromium/ci/chromium.android.star b/infra/config/subprojects/chromium/ci/chromium.android.star
index 4938469..a8ad42e 100644
--- a/infra/config/subprojects/chromium/ci/chromium.android.star
+++ b/infra/config/subprojects/chromium/ci/chromium.android.star
@@ -681,8 +681,12 @@
     ),
     targets = targets.bundle(
         targets = [
+            "cast_receiver_junit_tests",
             "chromium_android_cast_receiver",
-            "chromium_android_cast_tests",
+            "chromium_android_cast_receiver_arm_gtests",
+        ],
+        mixins = [
+            "has_native_resultdb_integration",
         ],
     ),
     # TODO(vigeni): Remove as configuration has been stablized.
@@ -734,8 +738,12 @@
     ),
     targets = targets.bundle(
         targets = [
+            "cast_receiver_junit_tests",
             "chromium_android_cast_receiver",
-            "chromium_android_cast_tests",
+            "chromium_android_cast_receiver_arm_gtests",
+        ],
+        mixins = [
+            "has_native_resultdb_integration",
         ],
     ),
     # TODO(vigeni): Remove as configuration has been stablized.
@@ -787,8 +795,12 @@
     ),
     targets = targets.bundle(
         targets = [
+            "cast_receiver_junit_tests",
             "chromium_android_cast_receiver",
-            "chromium_android_cast_tests",
+            "chromium_android_cast_receiver_arm64_gtests",
+        ],
+        mixins = [
+            "has_native_resultdb_integration",
         ],
     ),
     # TODO(vigeni): Remove as configuration has been stablized.
@@ -840,8 +852,12 @@
     ),
     targets = targets.bundle(
         targets = [
+            "cast_receiver_junit_tests",
             "chromium_android_cast_receiver",
-            "chromium_android_cast_tests",
+            "chromium_android_cast_receiver_arm64_gtests",
+        ],
+        mixins = [
+            "has_native_resultdb_integration",
         ],
     ),
     # TODO(vigeni): Remove as configuration has been stablized.
diff --git a/infra/config/targets/binaries.star b/infra/config/targets/binaries.star
index c1cc203..7c76aa0 100644
--- a/infra/config/targets/binaries.star
+++ b/infra/config/targets/binaries.star
@@ -302,8 +302,8 @@
 # TODO(issues.chromium.org/1516671): Remove unneeded cast_* suites.
 
 targets.binaries.console_test_launcher(
-    name = "cast_display_settings_unittests",
-    label = "//chromecast/ui/display_settings:cast_display_settings_unittests",
+    name = "cast_android_cma_backend_unittests",
+    label = "//chromecast/media/cma/backend/android:cast_android_cma_backend_unittests",
 )
 
 targets.binaries.console_test_launcher(
@@ -327,6 +327,11 @@
 )
 
 targets.binaries.console_test_launcher(
+    name = "cast_display_settings_unittests",
+    label = "//chromecast/ui/display_settings:cast_display_settings_unittests",
+)
+
+targets.binaries.console_test_launcher(
     name = "cast_graphics_unittests",
     label = "//chromecast/graphics:cast_graphics_unittests",
 )
diff --git a/infra/config/targets/bundles.star b/infra/config/targets/bundles.star
index 89523f0..8555997 100644
--- a/infra/config/targets/bundles.star
+++ b/infra/config/targets/bundles.star
@@ -618,9 +618,7 @@
         "cast_audio_backend_unittests",
         "cast_base_unittests",
         "cast_cast_core_unittests",
-        "cast_display_settings_unittests",
         "cast_media_unittests",
-        "cast_shell_unittests",
         "cast_unittests",
     ],
     mixins = [
@@ -675,8 +673,20 @@
 )
 
 targets.bundle(
-    name = "chromium_android_cast_receiver_gtests",
+    name = "chromium_android_cast_receiver_arm_gtests",
     targets = [
+        "cast_android_cma_backend_unittests",
+        "cast_receiver_gtests",
+    ],
+    mixins = [
+        "chromium_pixel_2_pie",
+    ],
+)
+
+targets.bundle(
+    name = "chromium_android_cast_receiver_arm64_gtests",
+    targets = [
+        "cast_android_cma_backend_unittests",
         "cast_receiver_gtests",
     ],
     mixins = [
@@ -685,22 +695,13 @@
 )
 
 targets.bundle(
-    name = "chromium_android_cast_tests",
-    targets = [
-        "cast_receiver_junit_tests",
-        "chromium_android_cast_receiver_gtests",
-    ],
-    mixins = [
-        "has_native_resultdb_integration",
-    ],
-)
-
-targets.bundle(
     name = "chromium_linux_cast_receiver_gtests",
     targets = [
         "cast_crash_unittests",
+        "cast_display_settings_unittests",
         "cast_graphics_unittests",
         "cast_receiver_gtests",
+        "cast_shell_unittests",
         "cast_shell_browsertests",
         "linux_flavor_specific_chromium_gtests",
     ],
diff --git a/infra/config/targets/tests.star b/infra/config/targets/tests.star
index 98ffe9c..3470769 100644
--- a/infra/config/targets/tests.star
+++ b/infra/config/targets/tests.star
@@ -390,6 +390,10 @@
 # needed.
 
 targets.tests.gtest_test(
+    name = "cast_android_cma_backend_unittests",
+)
+
+targets.tests.gtest_test(
     name = "cast_audio_backend_unittests",
 )
 
diff --git a/ios/chrome/browser/autofill/model/credit_card/credit_card_data.mm b/ios/chrome/browser/autofill/model/credit_card/credit_card_data.mm
index ffbb18a..23885fb 100644
--- a/ios/chrome/browser/autofill/model/credit_card/credit_card_data.mm
+++ b/ios/chrome/browser/autofill/model/credit_card/credit_card_data.mm
@@ -27,10 +27,7 @@
                   /* with_prefix=*/false));
     _accessibleCardName = [self accessibleCardName:creditCard];
     _backendIdentifier = base::SysUTF8ToNSString(creditCard.guid());
-    if (base::FeatureList::IsEnabled(
-            autofill::features::kAutofillEnableVirtualCards)) {
-      _recordType = creditCard.record_type();
-    }
+    _recordType = creditCard.record_type();
 
     if (icon.size.width > 0.0 && icon.size.width < 40.0 && icon.scale > 1.0) {
       // If the icon is smaller than desired, but is scaled, reduce the scale
diff --git a/ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_egtest.mm b/ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_egtest.mm
index e840ae9..8e4f2f3c 100644
--- a/ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_egtest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_egtest.mm
@@ -78,13 +78,6 @@
 
 #pragma mark - Setup
 
-- (AppLaunchConfiguration)appConfigurationForTestCase {
-  AppLaunchConfiguration config;
-  config.features_enabled.push_back(
-      autofill::features::kAutofillEnableVirtualCards);
-  return config;
-}
-
 - (void)setUp {
   [super setUp];
   [AutofillAppInterface setUpFakeCreditCardServer];
diff --git a/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_egtest.mm b/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_egtest.mm
index e42dddd..e8d476f 100644
--- a/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_egtest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_egtest.mm
@@ -89,13 +89,6 @@
 
 #pragma mark - Setup
 
-- (AppLaunchConfiguration)appConfigurationForTestCase {
-  AppLaunchConfiguration config;
-  config.features_enabled.push_back(
-      autofill::features::kAutofillEnableVirtualCards);
-  return config;
-}
-
 - (void)setUp {
   [super setUp];
   [AutofillAppInterface setUpFakeCreditCardServer];
diff --git a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_coordinator_unittest.mm b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_coordinator_unittest.mm
index 8d500d8..fc1ffd9 100644
--- a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_coordinator_unittest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_coordinator_unittest.mm
@@ -127,9 +127,6 @@
 // Test that using the primary button logs the correct exit reason when a
 // virtual card is used
 TEST_F(PaymentsSuggestionBottomSheetCoordinatorTest, PrimaryButtonVirtualCard) {
-  base::test::ScopedFeatureList feature_list_;
-  feature_list_.InitAndEnableFeature(
-      autofill::features::kAutofillEnableVirtualCards);
   base::HistogramTester histogram_tester;
 
   [coordinator_ start];
diff --git a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_egtest.mm b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_egtest.mm
index e31aa33..a4ad0d8ae 100644
--- a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_egtest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_egtest.mm
@@ -76,13 +76,6 @@
   [super tearDown];
 }
 
-- (AppLaunchConfiguration)appConfigurationForTestCase {
-  AppLaunchConfiguration config;
-  config.features_enabled.push_back(
-      autofill::features::kAutofillEnableVirtualCards);
-  return config;
-}
-
 // Matcher for the bottom sheet's "Continue" button.
 id<GREYMatcher> ContinueButton() {
   return chrome_test_util::StaticTextWithAccessibilityLabelId(
diff --git a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_mediator.mm b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_mediator.mm
index 5df3a56..3db085fa 100644
--- a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_mediator.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_mediator.mm
@@ -214,10 +214,8 @@
     // If the current card is enrolled to be a virtual card, create the virtual
     // card and add it to creditCardData array directly before the original
     // card.
-    if (base::FeatureList::IsEnabled(
-            autofill::features::kAutofillEnableVirtualCards) &&
-        creditCard->virtual_card_enrollment_state() ==
-            autofill::CreditCard::VirtualCardEnrollmentState::kEnrolled) {
+    if (creditCard->virtual_card_enrollment_state() ==
+        autofill::CreditCard::VirtualCardEnrollmentState::kEnrolled) {
       const autofill::CreditCard virtualCard =
           autofill::CreditCard::CreateVirtualCard(*creditCard);
       [creditCardData
@@ -270,27 +268,24 @@
 
   // Create a form suggestion containing the selected credit card's backend id
   // so that the suggestion provider can properly fill the form.
-  FormSuggestion* suggestion = [FormSuggestion
-              suggestionWithValue:nil
-                       minorValue:nil
-               displayDescription:nil
-                             icon:nil
-                             type:((base::FeatureList::IsEnabled(
-                                        autofill::features::
-                                            kAutofillEnableVirtualCards) &&
-                                    ([creditCardData recordType] ==
-                                     autofill::CreditCard::RecordType::
-                                         kVirtualCard))
-                                       ? autofill::SuggestionType::
-                                             kVirtualCreditCardEntry
-                                       : autofill::SuggestionType::
-                                             kCreditCardEntry)
-                backendIdentifier:[creditCardData backendIdentifier]
-      fieldByFieldFillingTypeUsed:autofill::EMPTY_TYPE
-                   requiresReauth:NO
-       acceptanceA11yAnnouncement:
-           base::SysUTF16ToNSString(l10n_util::GetStringUTF16(
-               IDS_AUTOFILL_A11Y_ANNOUNCE_FILLED_FORM))];
+  FormSuggestion* suggestion =
+      [FormSuggestion suggestionWithValue:nil
+                               minorValue:nil
+                       displayDescription:nil
+                                     icon:nil
+                                     type:([creditCardData recordType] ==
+                                                   autofill::CreditCard::
+                                                       RecordType::kVirtualCard
+                                               ? autofill::SuggestionType::
+                                                     kVirtualCreditCardEntry
+                                               : autofill::SuggestionType::
+                                                     kCreditCardEntry)
+                        backendIdentifier:[creditCardData backendIdentifier]
+              fieldByFieldFillingTypeUsed:autofill::EMPTY_TYPE
+                           requiresReauth:NO
+               acceptanceA11yAnnouncement:
+                   base::SysUTF16ToNSString(l10n_util::GetStringUTF16(
+                       IDS_AUTOFILL_A11Y_ANNOUNCE_FILLED_FORM))];
 
   [provider didSelectSuggestion:suggestion atIndex:index params:_params];
 }
diff --git a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_view_controller.mm b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_view_controller.mm
index b6fe86a..0bbad0d 100644
--- a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_view_controller.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_view_controller.mm
@@ -411,14 +411,9 @@
 
   // If we have the potential presence of a virtual card, the textLabel on its
   // own is no longer a unique identifier, so we include the description.
-  if (base::FeatureList::IsEnabled(
-          autofill::features::kAutofillEnableVirtualCards)) {
-    cell.accessibilityIdentifier =
-        [NSString stringWithFormat:@"%@ %@", cell.textLabel.text,
-                                   [self descriptionAtRow:indexPath.row]];
-  } else {
-    cell.accessibilityIdentifier = cell.textLabel.text;
-  }
+  cell.accessibilityIdentifier =
+      [NSString stringWithFormat:@"%@ %@", cell.textLabel.text,
+                                 [self descriptionAtRow:indexPath.row]];
 
   cell.separatorInset = [self separatorInsetForTableViewWidth:tableViewWidth
                                                   atIndexPath:indexPath];
diff --git a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/virtual_card_enrollment_bottom_sheet_egtest.mm b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/virtual_card_enrollment_bottom_sheet_egtest.mm
index cd483b1..ebae4f01 100644
--- a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/virtual_card_enrollment_bottom_sheet_egtest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/virtual_card_enrollment_bottom_sheet_egtest.mm
@@ -108,8 +108,6 @@
 
 - (AppLaunchConfiguration)appConfigurationForTestCase {
   AppLaunchConfiguration config;
-  config.features_enabled.push_back(
-      autofill::features::kAutofillEnableVirtualCards);
   if ([self
           isRunningTest:@selector
           (testVirtualCardEnrollmentShowsLoadingAndConfirmationAfterAcceptPushed
diff --git a/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller.mm b/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller.mm
index 40a0772..04d9613 100644
--- a/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller.mm
@@ -33,11 +33,6 @@
 
 namespace {
 
-BOOL VirtualCardFeatureEnabled() {
-  return base::FeatureList::IsEnabled(
-      autofill::features::kAutofillEnableVirtualCards);
-}
-
 typedef NS_ENUM(NSInteger, SectionIdentifier) {
   SectionIdentifierHeader = kSectionIdentifierEnumZero,
   SectionIdentifierInputs,
@@ -173,12 +168,10 @@
   [model setHeader:_headerItem
       forSectionWithIdentifier:SectionIdentifierHeader];
 
-  if (VirtualCardFeatureEnabled()) {
-    _cardInfoItem = [self createCardInfoItem];
-    if (_cardInfoItem != nil) {
-      [self.tableViewModel addItem:_cardInfoItem
-           toSectionWithIdentifier:SectionIdentifierHeader];
-    }
+  _cardInfoItem = [self createCardInfoItem];
+  if (_cardInfoItem != nil) {
+    [self.tableViewModel addItem:_cardInfoItem
+         toSectionWithIdentifier:SectionIdentifierHeader];
   }
 
   [model addSectionWithIdentifier:SectionIdentifierInputs];
diff --git a/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller_unittest.mm b/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller_unittest.mm
index 3a323c7..0d8b5264 100644
--- a/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller_unittest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller_unittest.mm
@@ -107,17 +107,9 @@
 };
 
 class CardUnmaskPromptViewControllerTest
-    : public LegacyChromeTableViewControllerTest,
-      public ::testing::WithParamInterface<bool> {
+    : public LegacyChromeTableViewControllerTest {
  protected:
-  CardUnmaskPromptViewControllerTest()
-      : virtual_card_enrollment_enabled_(GetParam()) {}
-
   void SetUp() override {
-    feature_list_.InitWithFeatureState(
-        autofill::features::kAutofillEnableVirtualCards,
-        virtual_card_enrollment_enabled_);
-
     LegacyChromeTableViewControllerTest::SetUp();
     root_view_controller_ = [[UIViewController alloc] init];
 
@@ -183,13 +175,11 @@
 
   // Fetches the card info cell from the tableView's datasource.
   TableViewDetailIconCell* GetCardInfoCell() {
-    return virtual_card_enrollment_enabled_ ? GetCell(/*item=*/0, /*section*/ 0)
-                                            : nil;
+    return GetCell(/*item=*/0, /*section*/ 0);
   }
 
   TableViewDetailIconItem* GetCardInfoItem() {
-    return virtual_card_enrollment_enabled_ ? GetItem(/*item=*/0, /*section=*/0)
-                                            : nil;
+    return GetItem(/*item=*/0, /*section=*/0);
   }
 
   // Fetches the CVC input cell from the tableView's datasource.
@@ -321,17 +311,15 @@
   void CheckTableViewModel(bool with_expiration_date_item) {
     // Check expected number of sections and items.
     EXPECT_EQ(NumberOfSections(), 2);
-    // First section only has a header when experiment is disabled. It has two
-    // (a header and a card info cell) when experiment is enabled.
-    EXPECT_EQ(NumberOfItemsInSection(0),
-              virtual_card_enrollment_enabled_ ? 1 : 0);
+    // First section has two items (a header and a card info cell).
+    EXPECT_EQ(NumberOfItemsInSection(0), 1);
     // CVC and expiration date fields if applicable.
     EXPECT_EQ(NumberOfItemsInSection(1), with_expiration_date_item ? 2 : 1);
 
     CheckHeaderAndInstructions();
 
     // Verifying card info is in model.
-    EXPECT_EQ(virtual_card_enrollment_enabled_, !!GetCardInfoItem());
+    EXPECT_TRUE(GetCardInfoItem());
 
     // Verifing CVC field is in model.
     EXPECT_TRUE(CVCInputItem());
@@ -362,20 +350,14 @@
   std::unique_ptr<autofill::TestPersonalDataManager> personal_data_manager_;
   ScopedKeyWindow scoped_window_;
   UIViewController* root_view_controller_;
-  base::test::ScopedFeatureList feature_list_;
   autofill::CreditCard card_ = autofill::test::GetMaskedServerCard();
-  bool virtual_card_enrollment_enabled_;
 };
 
-INSTANTIATE_TEST_SUITE_P(,
-                         CardUnmaskPromptViewControllerTest,
-                         ::testing::Bool());
-
 }  // namespace
 
 // Validates that the CVC form is displayed as the initial state of the
 // controller when the card is not expired.
-TEST_P(CardUnmaskPromptViewControllerTest, CVCFormDisplayedAsInitialState) {
+TEST_F(CardUnmaskPromptViewControllerTest, CVCFormDisplayedAsInitialState) {
   CheckController();
 
   CheckTableViewModel(/*with_expiration_date_item=*/false);
@@ -390,7 +372,7 @@
 
 // Validates that the Expiration Date form is displayed as the initial state of
 // the controller when the card is expired.
-TEST_P(CardUnmaskPromptViewControllerTest,
+TEST_F(CardUnmaskPromptViewControllerTest,
        ExpirationDateFormDisplayedAsInitialState) {
   // Recreate controller for the expiration date form state.
   ResetController();
@@ -409,7 +391,7 @@
 
 // Validates that the tableViewModel is properly setup for displaying update
 // expiration date form.
-TEST_P(CardUnmaskPromptViewControllerTest,
+TEST_F(CardUnmaskPromptViewControllerTest,
        UpdateExpirationDateFormStateHasExpectedItems) {
   auto* prompt_controller =
       static_cast<CardUnmaskPromptViewController*>(controller());
@@ -430,7 +412,7 @@
 
 // Validates the model is properly setup for displaying the expiration card link
 // in the controller's footer.
-TEST_P(CardUnmaskPromptViewControllerTest,
+TEST_F(CardUnmaskPromptViewControllerTest,
        ShowUpdateExpirationCardLinkAddsFooterWithLink) {
   // Footer shouldn't be in model before link is shown.
   EXPECT_FALSE(FooterItem());
@@ -445,7 +427,7 @@
 
 // Validates that the loading state displays an activity indicator in the
 // navigation bar and disables interactions with the input fields.
-TEST_P(CardUnmaskPromptViewControllerTest,
+TEST_F(CardUnmaskPromptViewControllerTest,
        ShowLoadingStateDisplaysActivityIndicatorAndDisablesInteractions) {
   auto* prompt_controller =
       static_cast<CardUnmaskPromptViewController*>(controller());
@@ -460,7 +442,7 @@
 }
 
 // Validates that an alert is presented for the error state.
-TEST_P(CardUnmaskPromptViewControllerTest, ShowErrorPresentsAlert) {
+TEST_F(CardUnmaskPromptViewControllerTest, ShowErrorPresentsAlert) {
   PresentController();
 
   auto* prompt_controller =
@@ -487,13 +469,13 @@
 
 // Verifies that submitting the CVC form forwards the CVC value to
 // CardUnmaskPromptController.
-TEST_P(CardUnmaskPromptViewControllerTest, TestSubmittingCVCForm) {
+TEST_F(CardUnmaskPromptViewControllerTest, TestSubmittingCVCForm) {
   CheckSubmittingForm(/*CVC=*/@"123");
 }
 
 // Verifies that submitting the update expiration date form forwards CVC and
 // expiration date to CardUnmaskPromptController/
-TEST_P(CardUnmaskPromptViewControllerTest, TestSubmittingExpirationDateForm) {
+TEST_F(CardUnmaskPromptViewControllerTest, TestSubmittingExpirationDateForm) {
   auto* prompt_controller =
       static_cast<CardUnmaskPromptViewController*>(controller());
   [prompt_controller showUpdateExpirationDateForm];
@@ -502,7 +484,7 @@
 }
 
 // Verifies that the controller is dismissed after an error.
-TEST_P(CardUnmaskPromptViewControllerTest,
+TEST_F(CardUnmaskPromptViewControllerTest,
        TestDismissingViewControllerAfterError) {
   auto* prompt_controller =
       static_cast<CardUnmaskPromptViewController*>(controller());
@@ -513,7 +495,7 @@
 }
 
 // Verifies that the expiration date link is displayed after an error.
-TEST_P(CardUnmaskPromptViewControllerTest,
+TEST_F(CardUnmaskPromptViewControllerTest,
        TestShowingUpdateExpirationDateLinkAfterError) {
   auto* prompt_controller =
       static_cast<CardUnmaskPromptViewController*>(controller());
@@ -524,7 +506,7 @@
 }
 
 // Verifies that the expiration date form is displayed after an error.
-TEST_P(CardUnmaskPromptViewControllerTest,
+TEST_F(CardUnmaskPromptViewControllerTest,
        TestShowingUpdateExpirationDateFormAfterError) {
   auto* prompt_controller =
       static_cast<CardUnmaskPromptViewController*>(controller());
@@ -538,7 +520,7 @@
 }
 
 // Verifies that the icon in the CVC input cell is hidden from Voice Over.
-TEST_P(CardUnmaskPromptViewControllerTest,
+TEST_F(CardUnmaskPromptViewControllerTest,
        TestCVCCellIconIsHiddenFromVoiceOver) {
   auto* CVC_cell = GetCVCInputCell();
   ASSERT_TRUE(CVC_cell);
@@ -547,7 +529,7 @@
 
 // Verifies that the textField in the CVC input cell does not accept more than 4
 // digits.
-TEST_P(CardUnmaskPromptViewControllerTest,
+TEST_F(CardUnmaskPromptViewControllerTest,
        TestCVCTextFieldRejectsTooLongCVCValues) {
   auto* CVC_field = GetCVCInputCell().textField;
   ASSERT_TRUE(CVC_field);
diff --git a/ios/chrome/browser/autofill/ui_bundled/cells/card_unmask_header_item.mm b/ios/chrome/browser/autofill/ui_bundled/cells/card_unmask_header_item.mm
index 70392b53..2439df05 100644
--- a/ios/chrome/browser/autofill/ui_bundled/cells/card_unmask_header_item.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/cells/card_unmask_header_item.mm
@@ -20,13 +20,10 @@
 // Spacing between elements.
 const CGFloat kUISpacing = 5;
 
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 // Height of the Google pay badge.
 const CGFloat kGooglePayBadgeHeight = 16;
-
-BOOL VirtualCardFeatureEnabled() {
-  return base::FeatureList::IsEnabled(
-      autofill::features::kAutofillEnableVirtualCards);
-}
+#endif
 
 }  // namespace
 
@@ -80,78 +77,48 @@
     CGFloat badgeAspectRatio = _googlePayBadgeImageView.image.size.width /
                                _googlePayBadgeImageView.image.size.height;
 
-    // If the virtual card feature is enabled, show the updated layout.
-    if (VirtualCardFeatureEnabled()) {
-      _titleLabel = [self createTitleLabel];
-      [self.contentView addSubview:_titleLabel];
+    _titleLabel = [self createTitleLabel];
+    [self.contentView addSubview:_titleLabel];
 
-      [NSLayoutConstraint activateConstraints:@[
-        // Google Pay Badge
-        [_googlePayBadgeImageView.topAnchor
-            constraintEqualToAnchor:self.contentView.topAnchor
-                           constant:kTableViewImagePadding],
-        [_googlePayBadgeImageView.centerXAnchor
-            constraintEqualToAnchor:self.contentView.centerXAnchor],
-        [_googlePayBadgeImageView.heightAnchor
-            constraintEqualToConstant:kTableViewIconImageSize],
-        [_googlePayBadgeImageView.widthAnchor
-            constraintEqualToAnchor:_googlePayBadgeImageView.heightAnchor
-                         multiplier:badgeAspectRatio],
+    [NSLayoutConstraint activateConstraints:@[
+      // Google Pay Badge
+      [_googlePayBadgeImageView.topAnchor
+          constraintEqualToAnchor:self.contentView.topAnchor
+                         constant:kTableViewImagePadding],
+      [_googlePayBadgeImageView.centerXAnchor
+          constraintEqualToAnchor:self.contentView.centerXAnchor],
+      [_googlePayBadgeImageView.heightAnchor
+          constraintEqualToConstant:kTableViewIconImageSize],
+      [_googlePayBadgeImageView.widthAnchor
+          constraintEqualToAnchor:_googlePayBadgeImageView.heightAnchor
+                       multiplier:badgeAspectRatio],
 
-        // Title label
-        [_titleLabel.topAnchor
-            constraintEqualToAnchor:_googlePayBadgeImageView.bottomAnchor
-                           constant:kTableViewImagePadding],
-        [_titleLabel.leadingAnchor
-            constraintEqualToAnchor:self.contentView.leadingAnchor
-                           constant:HorizontalPadding()],
-        [_titleLabel.trailingAnchor
-            constraintEqualToAnchor:self.contentView.trailingAnchor
-                           constant:-HorizontalPadding()],
+      // Title label
+      [_titleLabel.topAnchor
+          constraintEqualToAnchor:_googlePayBadgeImageView.bottomAnchor
+                         constant:kTableViewImagePadding],
+      [_titleLabel.leadingAnchor
+          constraintEqualToAnchor:self.contentView.leadingAnchor
+                         constant:HorizontalPadding()],
+      [_titleLabel.trailingAnchor
+          constraintEqualToAnchor:self.contentView.trailingAnchor
+                         constant:-HorizontalPadding()],
 
-        // Instructions label
-        [_instructionsLabel.topAnchor
-            constraintEqualToAnchor:_titleLabel.bottomAnchor
-                           constant:kUISpacing],
-        [_instructionsLabel.leadingAnchor
-            constraintEqualToAnchor:self.contentView.leadingAnchor
-                           constant:HorizontalPadding()],
-        [_instructionsLabel.trailingAnchor
-            constraintEqualToAnchor:self.contentView.trailingAnchor
-                           constant:-HorizontalPadding()],
-        [_instructionsLabel.bottomAnchor
-            constraintEqualToAnchor:self.contentView.bottomAnchor
-                           constant:-kTableViewLargeVerticalSpacing],
-      ]];
-    } else {
-      [NSLayoutConstraint activateConstraints:@[
-        // Instructions label
-        [_instructionsLabel.topAnchor
-            constraintEqualToAnchor:self.contentView.topAnchor
-                           constant:kTableViewVerticalSpacing],
-        [_instructionsLabel.leadingAnchor
-            constraintEqualToAnchor:self.contentView.leadingAnchor
-                           constant:HorizontalPadding()],
-        [_instructionsLabel.trailingAnchor
-            constraintEqualToAnchor:self.contentView.trailingAnchor
-                           constant:-HorizontalPadding()],
+      // Instructions label
+      [_instructionsLabel.topAnchor
+          constraintEqualToAnchor:_titleLabel.bottomAnchor
+                         constant:kUISpacing],
+      [_instructionsLabel.leadingAnchor
+          constraintEqualToAnchor:self.contentView.leadingAnchor
+                         constant:HorizontalPadding()],
+      [_instructionsLabel.trailingAnchor
+          constraintEqualToAnchor:self.contentView.trailingAnchor
+                         constant:-HorizontalPadding()],
+      [_instructionsLabel.bottomAnchor
+          constraintEqualToAnchor:self.contentView.bottomAnchor
+                         constant:-kTableViewLargeVerticalSpacing],
+    ]];
 
-        // Google Pay Badge
-        [_googlePayBadgeImageView.topAnchor
-            constraintEqualToAnchor:_instructionsLabel.bottomAnchor
-                           constant:kUISpacing],
-        [_googlePayBadgeImageView.leadingAnchor
-            constraintEqualToAnchor:_instructionsLabel.leadingAnchor],
-        [_googlePayBadgeImageView.heightAnchor
-            constraintEqualToConstant:kGooglePayBadgeHeight],
-        [_googlePayBadgeImageView.widthAnchor
-            constraintEqualToAnchor:_googlePayBadgeImageView.heightAnchor
-                         multiplier:badgeAspectRatio],
-        [_googlePayBadgeImageView.bottomAnchor
-            constraintEqualToAnchor:self.contentView.bottomAnchor
-                           constant:-kTableViewVerticalSpacing],
-      ]];
-    }
     if (@available(iOS 17, *)) {
       [self registerForTraitChanges:TraitCollectionSetForTraits(
                                         UITraitUserInterfaceStyle.self)
diff --git a/ios/chrome/browser/autofill/ui_bundled/cells/card_unmask_header_item_unittest.mm b/ios/chrome/browser/autofill/ui_bundled/cells/card_unmask_header_item_unittest.mm
index 17eb0b71..fc0849d 100644
--- a/ios/chrome/browser/autofill/ui_bundled/cells/card_unmask_header_item_unittest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/cells/card_unmask_header_item_unittest.mm
@@ -16,17 +16,11 @@
 
 class CardUnmaskHeaderItemTest : public PlatformTest {
  public:
-  CardUnmaskHeaderItemTest() {
-    scoped_feature_list_.InitAndEnableFeature(
-        autofill::features::kAutofillEnableVirtualCards);
-  }
+  CardUnmaskHeaderItemTest() {}
 
   CardUnmaskHeaderItemTest(const CardUnmaskHeaderItemTest&) = delete;
   CardUnmaskHeaderItemTest& operator=(const CardUnmaskHeaderItemTest&) = delete;
   ~CardUnmaskHeaderItemTest() override = default;
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Tests that the header subviews are set properly after a call to
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/card_view_controller_egtest.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/card_view_controller_egtest.mm
index 2830e79..1342108 100644
--- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/card_view_controller_egtest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/card_view_controller_egtest.mm
@@ -339,8 +339,6 @@
 
 - (AppLaunchConfiguration)appConfigurationForTestCase {
   AppLaunchConfiguration config;
-  config.features_enabled.push_back(
-      autofill::features::kAutofillEnableVirtualCards);
 
   if ([self shouldEnableKeyboardAccessoryUpgradeFeature]) {
     config.features_enabled.push_back(kIOSKeyboardAccessoryUpgrade);
@@ -348,12 +346,6 @@
     config.features_disabled.push_back(kIOSKeyboardAccessoryUpgrade);
   }
 
-  if ([self isRunningTest:@selector
-            (testCardChipButtonsAreAllVisibleWithVirtualCardsDisabled)]) {
-    config.features_disabled.push_back(
-        autofill::features::kAutofillEnableVirtualCards);
-  }
-
   return config;
 }
 
@@ -416,23 +408,6 @@
   CheckChipButtonsOfLocalCard();
 }
 
-// Tests that the saved card chip buttons are all visible in the card
-// table view controller, and that they have the right accessibility label when
-// the Virtual Cards feature is disabled. TODO(crbug.com/335736927): Delete this
-// test once the Virtual Cards feature is launched.
-- (void)testCardChipButtonsAreAllVisibleWithVirtualCardsDisabled {
-  [AutofillAppInterface saveLocalCreditCard];
-
-  // Bring up the keyboard
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
-      performAction:TapWebElementWithId(kFormElementName)];
-
-  // Open the payment method manual fill view.
-  OpenPaymentMethodManualFillView();
-
-  CheckChipButtonsOfLocalCard();
-}
-
 // Tests that the the "no payment methods found" message is visible when no
 // payment method suggestions are available.
 - (void)testNoPaymentMethodsFoundMessageIsVisibleWhenNoSuggestions {
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_cell.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_cell.mm
index 9787061..3b2e808 100644
--- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_cell.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_cell.mm
@@ -186,24 +186,12 @@
 // A labeled chip showing the card number.
 @property(nonatomic, strong) ManualFillLabeledChip* cardNumberLabeledChip;
 
-// A button showing the card number.
-@property(nonatomic, strong) UIButton* cardNumberButton;
-
 // A labeled chip showing the cardholder name.
 @property(nonatomic, strong) ManualFillLabeledChip* cardholderLabeledChip;
 
-// A button showing the cardholder name.
-@property(nonatomic, strong) UIButton* cardholderButton;
-
 // A labeled chip showing the card's expiration date.
 @property(nonatomic, strong) ManualFillLabeledChip* expirationDateLabeledChip;
 
-// A button showing the expiration month.
-@property(nonatomic, strong) UIButton* expirationMonthButton;
-
-// A button showing the expiration year.
-@property(nonatomic, strong) UIButton* expirationYearButton;
-
 // A labeled chip showing the card's CVC.
 @property(nonatomic, strong) ManualFillLabeledChip* CVCLabeledChip;
 
@@ -253,26 +241,13 @@
 
   self.cardLabel.text = @"";
 
-  if (base::FeatureList::IsEnabled(
-          autofill::features::kAutofillEnableVirtualCards)) {
-    self.virtualCardInstructionTextView.text = @"";
-    self.virtualCardInstructionTextView.hidden = NO;
+  self.virtualCardInstructionTextView.text = @"";
+  self.virtualCardInstructionTextView.hidden = NO;
 
-    [self.cardNumberLabeledChip prepareForReuse];
-    [self.expirationDateLabeledChip prepareForReuse];
-    [self.cardholderLabeledChip prepareForReuse];
-    [self.CVCLabeledChip prepareForReuse];
-  } else {
-    // TODO(crbug.com/330329960): Deprecate button use once
-    // kAutofillEnableVirtualCards is enabled.
-    [self.cardNumberButton setTitle:@"" forState:UIControlStateNormal];
-    [self.cardholderButton setTitle:@"" forState:UIControlStateNormal];
-    [self.expirationMonthButton setTitle:@"" forState:UIControlStateNormal];
-    [self.expirationYearButton setTitle:@"" forState:UIControlStateNormal];
-
-    self.cardNumberButton.hidden = NO;
-    self.cardholderButton.hidden = NO;
-  }
+  [self.cardNumberLabeledChip prepareForReuse];
+  [self.expirationDateLabeledChip prepareForReuse];
+  [self.cardholderLabeledChip prepareForReuse];
+  [self.CVCLabeledChip prepareForReuse];
 
   self.contentInjector = nil;
   self.navigationDelegate = nil;
@@ -359,73 +334,43 @@
 
   UILabel* expirationDateSeparatorLabel;
 
-  // If Virtual Cards are enabled, create UIViews with the labeled chips,
-  // otherwise use the buttons.
-  if (base::FeatureList::IsEnabled(
-          autofill::features::kAutofillEnableVirtualCards)) {
-    // Virtual card instruction textview is always created, but hidden for
-    // non-virtual cards.
-    self.virtualCardInstructionTextView =
-        [self createVirtualCardInstructionTextView];
-    [self.contentView addSubview:self.virtualCardInstructionTextView];
-    [accessibilityElements addObject:self.virtualCardInstructionTextView];
+  // Virtual card instruction textview is always created, but hidden for
+  // non-virtual cards.
+  self.virtualCardInstructionTextView =
+      [self createVirtualCardInstructionTextView];
+  [self.contentView addSubview:self.virtualCardInstructionTextView];
+  [accessibilityElements addObject:self.virtualCardInstructionTextView];
 
-    self.virtualCardInstructionsSeparator =
-        CreateGraySeparatorForContainer(self.contentView);
+  self.virtualCardInstructionsSeparator =
+      CreateGraySeparatorForContainer(self.contentView);
 
-    self.cardNumberLabeledChip = [[ManualFillLabeledChip alloc]
-        initSingleChipWithTarget:self
-                        selector:@selector(userDidTapCardNumber:)];
-    [self.contentView addSubview:self.cardNumberLabeledChip];
-    [accessibilityElements addObject:self.cardNumberLabeledChip.singleButton];
+  self.cardNumberLabeledChip = [[ManualFillLabeledChip alloc]
+      initSingleChipWithTarget:self
+                      selector:@selector(userDidTapCardNumber:)];
+  [self.contentView addSubview:self.cardNumberLabeledChip];
+  [accessibilityElements addObject:self.cardNumberLabeledChip.singleButton];
 
-    self.expirationDateLabeledChip = [[ManualFillLabeledChip alloc]
-        initExpirationDateChipWithTarget:self
-                           monthSelector:@selector(userDidTapExpirationMonth:)
-                            yearSelector:@selector(userDidTapExpirationYear:)];
-    [self.contentView addSubview:self.expirationDateLabeledChip];
-    [accessibilityElements
-        addObject:self.expirationDateLabeledChip.expirationMonthButton];
-    [accessibilityElements
-        addObject:self.expirationDateLabeledChip.expirationYearButton];
+  self.expirationDateLabeledChip = [[ManualFillLabeledChip alloc]
+      initExpirationDateChipWithTarget:self
+                         monthSelector:@selector(userDidTapExpirationMonth:)
+                          yearSelector:@selector(userDidTapExpirationYear:)];
+  [self.contentView addSubview:self.expirationDateLabeledChip];
+  [accessibilityElements
+      addObject:self.expirationDateLabeledChip.expirationMonthButton];
+  [accessibilityElements
+      addObject:self.expirationDateLabeledChip.expirationYearButton];
 
-    self.cardholderLabeledChip = [[ManualFillLabeledChip alloc]
-        initSingleChipWithTarget:self
-                        selector:@selector(userDidTapCardholderName:)];
-    [self.contentView addSubview:self.cardholderLabeledChip];
-    [accessibilityElements addObject:self.cardholderLabeledChip.singleButton];
+  self.cardholderLabeledChip = [[ManualFillLabeledChip alloc]
+      initSingleChipWithTarget:self
+                      selector:@selector(userDidTapCardholderName:)];
+  [self.contentView addSubview:self.cardholderLabeledChip];
+  [accessibilityElements addObject:self.cardholderLabeledChip.singleButton];
 
-    self.CVCLabeledChip = [[ManualFillLabeledChip alloc]
-        initSingleChipWithTarget:self
-                        selector:@selector(userDidTapCVC:)];
-    [self.contentView addSubview:self.CVCLabeledChip];
-    [accessibilityElements addObject:self.CVCLabeledChip.singleButton];
-  } else {
-    // TODO(crbug.com/330329960): Deprecate button use once
-    // kAutofillEnableVirtualCards is enabled.
-    self.cardNumberButton =
-        CreateChipWithSelectorAndTarget(@selector(userDidTapCardNumber:), self);
-    [self.contentView addSubview:self.cardNumberButton];
-    [accessibilityElements addObject:self.cardNumberButton];
-
-    self.expirationMonthButton =
-        CreateChipWithSelectorAndTarget(@selector(userDidTapCardInfo:), self);
-    [self.contentView addSubview:self.expirationMonthButton];
-    [accessibilityElements addObject:self.expirationMonthButton];
-
-    expirationDateSeparatorLabel = [self createExpirationSeparatorLabel];
-    [self.contentView addSubview:expirationDateSeparatorLabel];
-
-    self.expirationYearButton =
-        CreateChipWithSelectorAndTarget(@selector(userDidTapCardInfo:), self);
-    [self.contentView addSubview:self.expirationYearButton];
-    [accessibilityElements addObject:self.expirationYearButton];
-
-    self.cardholderButton =
-        CreateChipWithSelectorAndTarget(@selector(userDidTapCardInfo:), self);
-    [self.contentView addSubview:self.cardholderButton];
-    [accessibilityElements addObject:self.cardholderButton];
-  }
+  self.CVCLabeledChip = [[ManualFillLabeledChip alloc]
+      initSingleChipWithTarget:self
+                      selector:@selector(userDidTapCVC:)];
+  [self.contentView addSubview:self.CVCLabeledChip];
+  [accessibilityElements addObject:self.CVCLabeledChip.singleButton];
 
   self.autofillFormButton = CreateAutofillFormButton();
   [self.contentView addSubview:self.autofillFormButton];
@@ -446,61 +391,32 @@
   AppendHorizontalConstraintsForViews(staticConstraints, @[ self.headerView ],
                                       self.layoutGuide);
 
-  // If Virtual Cards are enabled, position the labeled chips, else position the
-  // regular buttons.
   self.gPayIcon = [self createGPayIcon];
   [self.contentView addSubview:self.gPayIcon];
-  if (base::FeatureList::IsEnabled(
-          autofill::features::kAutofillEnableVirtualCards)) {
-    AppendHorizontalConstraintsForViews(
-        staticConstraints, @[ self.virtualCardInstructionTextView ],
-        self.layoutGuide);
-    AppendHorizontalConstraintsForViews(
-        staticConstraints, @[ self.cardNumberLabeledChip ], self.layoutGuide,
-        kChipsHorizontalMargin,
-        AppendConstraintsHorizontalEqualOrSmallerThanGuide, self.gPayIcon);
-    AppendHorizontalConstraintsForViews(
-        staticConstraints, @[ self.expirationDateLabeledChip ],
-        self.layoutGuide, kChipsHorizontalMargin,
-        AppendConstraintsHorizontalEqualOrSmallerThanGuide);
-    AppendHorizontalConstraintsForViews(
-        staticConstraints, @[ self.cardholderLabeledChip ], self.layoutGuide,
-        kChipsHorizontalMargin,
-        AppendConstraintsHorizontalEqualOrSmallerThanGuide);
-    AppendHorizontalConstraintsForViews(
-        staticConstraints, @[ self.CVCLabeledChip ], self.layoutGuide,
-        kChipsHorizontalMargin,
-        AppendConstraintsHorizontalEqualOrSmallerThanGuide);
-    [staticConstraints
-        addObject:[self.gPayIcon.topAnchor
-                      constraintEqualToAnchor:self.cardNumberLabeledChip
-                                                  .topAnchor
-                                     constant:GPayIconTopAnchorOffset()]];
-  } else {
-    // TODO(crbug.com/330329960): Deprecate button use once
-    // kAutofillEnableVirtualCards is enabled.
-    AppendHorizontalConstraintsForViews(
-        staticConstraints, @[ self.cardNumberButton ], self.layoutGuide,
-        kChipsHorizontalMargin,
-        AppendConstraintsHorizontalEqualOrSmallerThanGuide, self.gPayIcon);
-    AppendHorizontalConstraintsForViews(
-        staticConstraints,
-        @[
-          self.expirationMonthButton, expirationDateSeparatorLabel,
-          self.expirationYearButton
-        ],
-        self.layoutGuide, kChipsHorizontalMargin,
-        AppendConstraintsHorizontalSyncBaselines |
-            AppendConstraintsHorizontalEqualOrSmallerThanGuide);
-    AppendHorizontalConstraintsForViews(
-        staticConstraints, @[ self.cardholderButton ], self.layoutGuide,
-        kChipsHorizontalMargin,
-        AppendConstraintsHorizontalEqualOrSmallerThanGuide);
-    [staticConstraints
-        addObject:[self.gPayIcon.topAnchor
-                      constraintEqualToAnchor:self.cardNumberButton.topAnchor
-                                     constant:GPayIconTopAnchorOffset()]];
-  }
+
+  AppendHorizontalConstraintsForViews(staticConstraints,
+                                      @[ self.virtualCardInstructionTextView ],
+                                      self.layoutGuide);
+  AppendHorizontalConstraintsForViews(
+      staticConstraints, @[ self.cardNumberLabeledChip ], self.layoutGuide,
+      kChipsHorizontalMargin,
+      AppendConstraintsHorizontalEqualOrSmallerThanGuide, self.gPayIcon);
+  AppendHorizontalConstraintsForViews(
+      staticConstraints, @[ self.expirationDateLabeledChip ], self.layoutGuide,
+      kChipsHorizontalMargin,
+      AppendConstraintsHorizontalEqualOrSmallerThanGuide);
+  AppendHorizontalConstraintsForViews(
+      staticConstraints, @[ self.cardholderLabeledChip ], self.layoutGuide,
+      kChipsHorizontalMargin,
+      AppendConstraintsHorizontalEqualOrSmallerThanGuide);
+  AppendHorizontalConstraintsForViews(
+      staticConstraints, @[ self.CVCLabeledChip ], self.layoutGuide,
+      kChipsHorizontalMargin,
+      AppendConstraintsHorizontalEqualOrSmallerThanGuide);
+  [staticConstraints
+      addObject:[self.gPayIcon.topAnchor
+                    constraintEqualToAnchor:self.cardNumberLabeledChip.topAnchor
+                                   constant:GPayIconTopAnchorOffset()]];
 
   AppendHorizontalConstraintsForViews(
       staticConstraints, @[ self.autofillFormButton ], self.layoutGuide);
@@ -531,105 +447,63 @@
       stringWithFormat:@"%@ %@", manual_fill::kPaymentManualFillGPayLogoID,
                        card.networkAndLastFourDigits];
 
-  // If Virtual Cards are enabled set text for labeled chips, else set text for
-  // buttons.
-  if (base::FeatureList::IsEnabled(
-          autofill::features::kAutofillEnableVirtualCards)) {
-    NSMutableAttributedString* attributedString =
-        [self createCardLabelAttributedText:card];
-    self.cardLabel.numberOfLines = 0;
-    self.cardLabel.attributedText = attributedString;
-    self.cardLabel.accessibilityIdentifier = attributedString.string;
-    if (card.recordType == kVirtualCard) {
-      self.virtualCardInstructionTextView.attributedText =
-          [self createvirtualCardInstructionTextViewAttributedText];
-      self.virtualCardInstructionTextView.backgroundColor = UIColor.clearColor;
-    }
-    [self.cardNumberLabeledChip
-        setLabelText:
-            (card.recordType == kVirtualCard
-                 ? l10n_util::GetNSString(
-                       IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_CARD_NUMBER_LABEL_IOS)
-                 : l10n_util::GetNSString(
-                       IDS_AUTOFILL_REGULAR_CARD_MANUAL_FALLBACK_BUBBLE_CARD_NUMBER_LABEL_IOS))
-        buttonTitles:@[ card.obfuscatedNumber ]];
-    [self.expirationDateLabeledChip
-        setLabelText:
-            l10n_util::GetNSString(
-                IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_EXP_DATE_LABEL_IOS)
-        buttonTitles:@[ card.expirationMonth, card.expirationYear ]];
-    [self.cardholderLabeledChip
-        setLabelText:
-            l10n_util::GetNSString(
-                IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_NAME_ON_CARD_LABEL_IOS)
-        buttonTitles:@[ card.cardHolder ]];
-    if (card.recordType == kVirtualCard) {
-      [self.CVCLabeledChip
-          setLabelText:
-              l10n_util::GetNSString(
-                  IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_CVC_LABEL_IOS)
-          buttonTitles:@[ card.CVC ]];
-    }
-
-    if (IsKeyboardAccessoryUpgradeEnabled()) {
-      self.cardNumberLabeledChip.singleButton.accessibilityLabel =
-          l10n_util::GetNSStringF(
-              IDS_IOS_MANUAL_FALLBACK_CARD_NUMBER_CHIP_ACCESSIBILITY_LABEL,
-              base::SysNSStringToUTF16(
-                  CardNumberLastFourDigits(card.obfuscatedNumber)));
-      self.expirationDateLabeledChip.expirationMonthButton.accessibilityLabel =
-          l10n_util::GetNSStringF(
-              IDS_IOS_MANUAL_FALLBACK_EXPIRATION_MONTH_CHIP_ACCESSIBILITY_LABEL,
-              base::SysNSStringToUTF16(card.expirationMonth));
-      self.expirationDateLabeledChip.expirationYearButton.accessibilityLabel =
-          l10n_util::GetNSStringF(
-              IDS_IOS_MANUAL_FALLBACK_EXPIRATION_YEAR_CHIP_ACCESSIBILITY_LABEL,
-              base::SysNSStringToUTF16(card.expirationYear));
-      self.cardholderLabeledChip.singleButton.accessibilityLabel =
-          l10n_util::GetNSStringF(
-              IDS_IOS_MANUAL_FALLBACK_CARDHOLDER_CHIP_ACCESSIBILITY_LABEL,
-              base::SysNSStringToUTF16(card.cardHolder));
-      self.CVCLabeledChip.singleButton.accessibilityLabel =
+  NSMutableAttributedString* attributedString =
+      [self createCardLabelAttributedText:card];
+  self.cardLabel.numberOfLines = 0;
+  self.cardLabel.attributedText = attributedString;
+  self.cardLabel.accessibilityIdentifier = attributedString.string;
+  if (card.recordType == kVirtualCard) {
+    self.virtualCardInstructionTextView.attributedText =
+        [self createvirtualCardInstructionTextViewAttributedText];
+    self.virtualCardInstructionTextView.backgroundColor = UIColor.clearColor;
+  }
+  [self.cardNumberLabeledChip
+      setLabelText:
+          (card.recordType == kVirtualCard
+               ? l10n_util::GetNSString(
+                     IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_CARD_NUMBER_LABEL_IOS)
+               : l10n_util::GetNSString(
+                     IDS_AUTOFILL_REGULAR_CARD_MANUAL_FALLBACK_BUBBLE_CARD_NUMBER_LABEL_IOS))
+      buttonTitles:@[ card.obfuscatedNumber ]];
+  [self.expirationDateLabeledChip
+      setLabelText:
           l10n_util::GetNSString(
-              IDS_IOS_MANUAL_FALLBACK_CVC_CHIP_ACCESSIBILITY_LABEL);
-    }
-  } else {
-    // TODO(crbug.com/330329960): Deprecate button use once
-    // kAutofillEnableVirtualCards is enabled.
-    NSString* cardName = [self createCardName:card];
-    self.cardLabel.attributedText = [[NSMutableAttributedString alloc]
-        initWithString:cardName
-            attributes:@{
-              NSForegroundColorAttributeName :
-                  [UIColor colorNamed:kTextPrimaryColor],
-              NSFontAttributeName :
-                  [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]
-            }];
+              IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_EXP_DATE_LABEL_IOS)
+      buttonTitles:@[ card.expirationMonth, card.expirationYear ]];
+  [self.cardholderLabeledChip
+      setLabelText:
+          l10n_util::GetNSString(
+              IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_NAME_ON_CARD_LABEL_IOS)
+      buttonTitles:@[ card.cardHolder ]];
+  if (card.recordType == kVirtualCard) {
+    [self.CVCLabeledChip
+        setLabelText:
+            l10n_util::GetNSString(
+                IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_CVC_LABEL_IOS)
+        buttonTitles:@[ card.CVC ]];
+  }
 
-    [self.cardNumberButton setTitle:card.obfuscatedNumber
-                           forState:UIControlStateNormal];
-    [self.expirationMonthButton setTitle:card.expirationMonth
-                                forState:UIControlStateNormal];
-    [self.expirationYearButton setTitle:card.expirationYear
-                               forState:UIControlStateNormal];
-    [self.cardholderButton setTitle:card.cardHolder
-                           forState:UIControlStateNormal];
-
-    if (IsKeyboardAccessoryUpgradeEnabled()) {
-      self.cardNumberButton.accessibilityLabel = l10n_util::GetNSStringF(
-          IDS_IOS_MANUAL_FALLBACK_CARD_NUMBER_CHIP_ACCESSIBILITY_LABEL,
-          base::SysNSStringToUTF16(
-              CardNumberLastFourDigits(card.obfuscatedNumber)));
-      self.expirationMonthButton.accessibilityLabel = l10n_util::GetNSStringF(
-          IDS_IOS_MANUAL_FALLBACK_EXPIRATION_MONTH_CHIP_ACCESSIBILITY_LABEL,
-          base::SysNSStringToUTF16(card.expirationMonth));
-      self.expirationYearButton.accessibilityLabel = l10n_util::GetNSStringF(
-          IDS_IOS_MANUAL_FALLBACK_EXPIRATION_YEAR_CHIP_ACCESSIBILITY_LABEL,
-          base::SysNSStringToUTF16(card.expirationYear));
-      self.cardholderButton.accessibilityLabel = l10n_util::GetNSStringF(
-          IDS_IOS_MANUAL_FALLBACK_CARDHOLDER_CHIP_ACCESSIBILITY_LABEL,
-          base::SysNSStringToUTF16(card.cardHolder));
-    }
+  if (IsKeyboardAccessoryUpgradeEnabled()) {
+    self.cardNumberLabeledChip.singleButton.accessibilityLabel =
+        l10n_util::GetNSStringF(
+            IDS_IOS_MANUAL_FALLBACK_CARD_NUMBER_CHIP_ACCESSIBILITY_LABEL,
+            base::SysNSStringToUTF16(
+                CardNumberLastFourDigits(card.obfuscatedNumber)));
+    self.expirationDateLabeledChip.expirationMonthButton.accessibilityLabel =
+        l10n_util::GetNSStringF(
+            IDS_IOS_MANUAL_FALLBACK_EXPIRATION_MONTH_CHIP_ACCESSIBILITY_LABEL,
+            base::SysNSStringToUTF16(card.expirationMonth));
+    self.expirationDateLabeledChip.expirationYearButton.accessibilityLabel =
+        l10n_util::GetNSStringF(
+            IDS_IOS_MANUAL_FALLBACK_EXPIRATION_YEAR_CHIP_ACCESSIBILITY_LABEL,
+            base::SysNSStringToUTF16(card.expirationYear));
+    self.cardholderLabeledChip.singleButton.accessibilityLabel =
+        l10n_util::GetNSStringF(
+            IDS_IOS_MANUAL_FALLBACK_CARDHOLDER_CHIP_ACCESSIBILITY_LABEL,
+            base::SysNSStringToUTF16(card.cardHolder));
+    self.CVCLabeledChip.singleButton.accessibilityLabel =
+        l10n_util::GetNSString(
+            IDS_IOS_MANUAL_FALLBACK_CVC_CHIP_ACCESSIBILITY_LABEL);
   }
 }
 
@@ -652,64 +526,43 @@
   NSMutableArray<UIView*>* cardInfoGroupVerticalLeadChips =
       [[NSMutableArray alloc] init];
 
-  // If Virtual Cards are enabled add labeled chips to be positioned
-  // else just add the buttons.
-  if (base::FeatureList::IsEnabled(
-          autofill::features::kAutofillEnableVirtualCards)) {
-    // Virtual card instruction.
-    if (card.recordType == kVirtualCard) {
+  // Virtual card instruction.
+  if (card.recordType == kVirtualCard) {
+    AddViewToVerticalLeadViews(
+        self.virtualCardInstructionTextView,
+        ManualFillCellView::ElementType::kVirtualCardInstructions,
+        verticalLeadViews);
+    if (IsKeyboardAccessoryUpgradeEnabled()) {
       AddViewToVerticalLeadViews(
-          self.virtualCardInstructionTextView,
-          ManualFillCellView::ElementType::kVirtualCardInstructions,
+          self.virtualCardInstructionsSeparator,
+          ManualFillCellView::ElementType::kVirtualCardInstructionsSeparator,
           verticalLeadViews);
-      if (IsKeyboardAccessoryUpgradeEnabled()) {
-        AddViewToVerticalLeadViews(
-            self.virtualCardInstructionsSeparator,
-            ManualFillCellView::ElementType::kVirtualCardInstructionsSeparator,
-            verticalLeadViews);
-        self.virtualCardInstructionsSeparator.hidden = NO;
-      }
-      self.virtualCardInstructionTextView.hidden = NO;
-    } else {
-      self.virtualCardInstructionTextView.hidden = YES;
-      self.virtualCardInstructionsSeparator.hidden = YES;
+      self.virtualCardInstructionsSeparator.hidden = NO;
     }
-
-    // Card number labeled chip button.
-    [self addChipButton:self.cardNumberLabeledChip
-            toChipGroup:cardInfoGroupVerticalLeadChips
-                 ifTrue:(card.obfuscatedNumber.length > 0)];
-
-    // Expiration date labeled chip button.
-    [cardInfoGroupVerticalLeadChips addObject:self.expirationDateLabeledChip];
-
-    // Card holder labeled chip button.
-    [self addChipButton:self.cardholderLabeledChip
-            toChipGroup:cardInfoGroupVerticalLeadChips
-                 ifTrue:(card.cardHolder.length > 0)];
-
-    // CVC labeled chip button.
-    [self addChipButton:self.CVCLabeledChip
-            toChipGroup:cardInfoGroupVerticalLeadChips
-                 ifTrue:(card.CVC.length > 0)];
+    self.virtualCardInstructionTextView.hidden = NO;
   } else {
-    // TODO(crbug.com/330329960): Deprecate button use once
-    // kAutofillEnableVirtualCards is enabled.
-
-    // Card number chip button.
-    [self addChipButton:self.cardNumberButton
-            toChipGroup:cardInfoGroupVerticalLeadChips
-                 ifTrue:(card.obfuscatedNumber.length > 0)];
-
-    // Expiration date chip button.
-    [cardInfoGroupVerticalLeadChips addObject:self.expirationMonthButton];
-
-    // Card holder chip button.
-    [self addChipButton:self.cardholderButton
-            toChipGroup:cardInfoGroupVerticalLeadChips
-                 ifTrue:(card.cardHolder.length > 0)];
+    self.virtualCardInstructionTextView.hidden = YES;
+    self.virtualCardInstructionsSeparator.hidden = YES;
   }
 
+  // Card number labeled chip button.
+  [self addChipButton:self.cardNumberLabeledChip
+          toChipGroup:cardInfoGroupVerticalLeadChips
+               ifTrue:(card.obfuscatedNumber.length > 0)];
+
+  // Expiration date labeled chip button.
+  [cardInfoGroupVerticalLeadChips addObject:self.expirationDateLabeledChip];
+
+  // Card holder labeled chip button.
+  [self addChipButton:self.cardholderLabeledChip
+          toChipGroup:cardInfoGroupVerticalLeadChips
+               ifTrue:(card.cardHolder.length > 0)];
+
+  // CVC labeled chip button.
+  [self addChipButton:self.CVCLabeledChip
+          toChipGroup:cardInfoGroupVerticalLeadChips
+               ifTrue:(card.CVC.length > 0)];
+
   AddChipGroupsToVerticalLeadViews(@[ cardInfoGroupVerticalLeadChips ],
                                    verticalLeadViews);
 
@@ -736,14 +589,8 @@
     return;
   }
 
-  if (base::FeatureList::IsEnabled(
-          autofill::features::kAutofillEnableVirtualCards)) {
-    base::RecordAction(base::UserMetricsAction(
-        [self createMetricsAction:@"SelectCardNumber"]));
-  } else {
-    base::RecordAction(
-        base::UserMetricsAction("ManualFallback_CreditCard_SelectCardNumber"));
-  }
+  base::RecordAction(
+      base::UserMetricsAction([self createMetricsAction:@"SelectCardNumber"]));
 
   if (self.card.canFillDirectly) {
     [self.contentInjector userDidPickContent:number
@@ -756,25 +603,6 @@
   }
 }
 
-// TODO(crbug.com/330329960): Deprecate this method once
-// kAutofillEnableVirtualCards is enabled.
-- (void)userDidTapCardInfo:(UIButton*)sender {
-  const char* metricsAction = nullptr;
-  if (sender == self.cardholderButton) {
-    metricsAction = "ManualFallback_CreditCard_SelectCardholderName";
-  } else if (sender == self.expirationMonthButton) {
-    metricsAction = "ManualFallback_CreditCard_SelectExpirationMonth";
-  } else if (sender == self.expirationYearButton) {
-    metricsAction = "ManualFallback_CreditCard_SelectExpirationYear";
-  }
-  DCHECK(metricsAction);
-  base::RecordAction(base::UserMetricsAction(metricsAction));
-
-  [self.contentInjector userDidPickContent:sender.titleLabel.text
-                             passwordField:NO
-                             requiresHTTPS:NO];
-}
-
 - (void)userDidTapCardholderName:(UIButton*)sender {
   base::RecordAction(base::UserMetricsAction(
       [self createMetricsAction:@"SelectCardholderName"]));
@@ -830,7 +658,6 @@
       base::UserMetricsAction("ManualFallback_CreditCard_SuggestionAccepted"));
 
   autofill::SuggestionType type =
-      autofill::VirtualCardFeatureEnabled() &&
               [self.card recordType] == kVirtualCard
           ? autofill::SuggestionType::kVirtualCreditCardEntry
           : autofill::SuggestionType::kCreditCardEntry;
@@ -942,17 +769,6 @@
   return virtualCardInstructionTextView;
 }
 
-// TODO(crbug.com/330329960): Deprecate this method use once
-// kAutofillEnableVirtualCards is enabled.
-- (UILabel*)createExpirationSeparatorLabel {
-  UILabel* expirationSeparatorLabel = CreateLabel();
-  expirationSeparatorLabel.font =
-      [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
-  [expirationSeparatorLabel setTextColor:[UIColor colorNamed:kSeparatorColor]];
-  expirationSeparatorLabel.text = @"/";
-  return expirationSeparatorLabel;
-}
-
 // Creates and configures the card icon image view.
 - (UIImageView*)createCardIcon {
   UIImageView* cardIcon = [[UIImageView alloc] init];
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_mediator.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_mediator.mm
index 258a8a3..a2ef42b 100644
--- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_mediator.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_mediator.mm
@@ -168,10 +168,8 @@
   // enrolled to have one.
   for (const CreditCard& card : _cards) {
     // Virtual cards are ordered directly before their original card.
-    if (base::FeatureList::IsEnabled(
-            autofill::features::kAutofillEnableVirtualCards) &&
-        card.virtual_card_enrollment_state() ==
-            CreditCard::VirtualCardEnrollmentState::kEnrolled) {
+    if (card.virtual_card_enrollment_state() ==
+        CreditCard::VirtualCardEnrollmentState::kEnrolled) {
       CreditCard virtualCard = CreditCard::CreateVirtualCard(card);
       cardsToPresent.push_back(virtualCard);
     }
@@ -324,24 +322,19 @@
       initWithCreditCard:card
                     icon:[self iconForCreditCard:card]];
   NSString* fillValue;
-  if (base::FeatureList::IsEnabled(
-          autofill::features::kAutofillEnableVirtualCards)) {
-    switch (fieldType) {
-      case PaymentFieldType::kCardNumber:
-        fillValue = manualFillCreditCard.number;
-        break;
-      case PaymentFieldType::kExpirationMonth:
-        fillValue = manualFillCreditCard.expirationMonth;
-        break;
-      case PaymentFieldType::kExpirationYear:
-        fillValue = manualFillCreditCard.expirationYear;
-        break;
-      case PaymentFieldType::kCVC:
-        fillValue = manualFillCreditCard.CVC;
-        break;
-    }
-  } else {
-    fillValue = manualFillCreditCard.number;
+  switch (fieldType) {
+    case PaymentFieldType::kCardNumber:
+      fillValue = manualFillCreditCard.number;
+      break;
+    case PaymentFieldType::kExpirationMonth:
+      fillValue = manualFillCreditCard.expirationMonth;
+      break;
+    case PaymentFieldType::kExpirationYear:
+      fillValue = manualFillCreditCard.expirationYear;
+      break;
+    case PaymentFieldType::kCVC:
+      fillValue = manualFillCreditCard.CVC;
+      break;
   }
 
   // Don't replace the locked card with the unlocked one, so the user will
diff --git a/ios/chrome/browser/discover_feed/model/BUILD.gn b/ios/chrome/browser/discover_feed/model/BUILD.gn
index 617f71e7..3dbf57b 100644
--- a/ios/chrome/browser/discover_feed/model/BUILD.gn
+++ b/ios/chrome/browser/discover_feed/model/BUILD.gn
@@ -81,6 +81,7 @@
     "//ios/chrome/browser/ntp/shared/metrics",
     "//ios/chrome/browser/push_notification/model:push_notification_client_id",
     "//ios/chrome/browser/push_notification/model:push_notification_service",
+    "//ios/chrome/browser/push_notification/model:push_notification_util",
     "//ios/chrome/browser/search_engines/model:template_url_service_factory",
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/shared/model/profile",
diff --git a/ios/chrome/browser/discover_feed/model/discover_feed_app_agent.mm b/ios/chrome/browser/discover_feed/model/discover_feed_app_agent.mm
index 95771dc..78574201 100644
--- a/ios/chrome/browser/discover_feed/model/discover_feed_app_agent.mm
+++ b/ios/chrome/browser/discover_feed/model/discover_feed_app_agent.mm
@@ -24,8 +24,8 @@
 #import "ios/chrome/browser/discover_feed/model/discover_feed_service_factory.h"
 #import "ios/chrome/browser/discover_feed/model/feed_constants.h"
 #import "ios/chrome/browser/ntp/shared/metrics/feed_metrics_recorder.h"
-#import "ios/chrome/browser/push_notification/model/provisional_push_notification_util.h"
 #import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_util.h"
 #import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
 #import "ios/chrome/browser/shared/model/application_context/application_context.h"
 #import "ios/chrome/browser/shared/model/profile/profile_ios.h"
@@ -383,13 +383,8 @@
   if (!IsFeedBackgroundRefreshCompletedNotificationEnabled()) {
     return;
   }
-  UNUserNotificationCenter* center =
-      UNUserNotificationCenter.currentNotificationCenter;
-  [center requestAuthorizationWithOptions:(UNAuthorizationOptionProvisional |
-                                           UNAuthorizationOptionAlert |
-                                           UNAuthorizationOptionSound)
-                        completionHandler:^(BOOL granted, NSError* error){
-                        }];
+
+  [PushNotificationUtil enableProvisionalPushNotificationPermission:nil];
 }
 
 // Requests OS to send a local user notification with `title`.
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index fe204e14..25089240 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -1467,11 +1467,6 @@
     {"top-toolbar-theme-color", flag_descriptions::kThemeColorInTopToolbarName,
      flag_descriptions::kThemeColorInTopToolbarDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kThemeColorInTopToolbar)},
-    {"autofill-enable-virtual-cards",
-     flag_descriptions::kAutofillEnableVirtualCardsName,
-     flag_descriptions::kAutofillEnableVirtualCardsDescription,
-     flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(autofill::features::kAutofillEnableVirtualCards)},
     {"privacy-guide-ios", flag_descriptions::kPrivacyGuideIosName,
      flag_descriptions::kPrivacyGuideIosDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kPrivacyGuideIos)},
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 3961929..b420706 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -132,12 +132,6 @@
 const char kAutofillEnableVerveCardSupportDescription[] =
     "When enabled, Verve-branded card art will be shown for Verve cards.";
 
-const char kAutofillEnableVirtualCardsName[] =
-    "Enable virtual card enrollment and retrieval";
-const char kAutofillEnableVirtualCardsDescription[] =
-    "When enabled, virtual card enrollment and retrieval will be available on "
-    "Bling.";
-
 const char kAutofillIsolatedWorldForJavascriptIOSName[] =
     "Isolated content world for Autofill";
 const char kAutofillIsolatedWorldForJavascriptIOSDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 71f6601..592d63b 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -104,11 +104,6 @@
 extern const char kAutofillEnableVerveCardSupportName[];
 extern const char kAutofillEnableVerveCardSupportDescription[];
 
-// Title and description for flag to enable virtual card enrollment and
-// retrieval.
-extern const char kAutofillEnableVirtualCardsName[];
-extern const char kAutofillEnableVirtualCardsDescription[];
-
 // Title and description for the flag to control whether to use the
 // isolated content world instead of the page content world for the Autofill JS
 // feature scripts.
diff --git a/ios/chrome/browser/ntp/shared/metrics/home_metrics.mm b/ios/chrome/browser/ntp/shared/metrics/home_metrics.mm
index a113668..da56de7 100644
--- a/ios/chrome/browser/ntp/shared/metrics/home_metrics.mm
+++ b/ios/chrome/browser/ntp/shared/metrics/home_metrics.mm
@@ -198,6 +198,8 @@
       // Ephemeral Card
     case ContentSuggestionsModuleType::kPriceTrackingPromo:
     case ContentSuggestionsModuleType::kInvalid:
+    case ContentSuggestionsModuleType::kTipsWithProductImage:
+    case ContentSuggestionsModuleType::kTips:
       break;
   }
   UMA_HISTOGRAM_ENUMERATION(kMagicStackTopModuleImpressionHistogram,
diff --git a/ios/chrome/browser/push_notification/model/push_notification_delegate.mm b/ios/chrome/browser/push_notification/model/push_notification_delegate.mm
index a789c859..e1c8507 100644
--- a/ios/chrome/browser/push_notification/model/push_notification_delegate.mm
+++ b/ios/chrome/browser/push_notification/model/push_notification_delegate.mm
@@ -340,11 +340,7 @@
     // Send an NAU on every foreground to report the OS Auth Settings.
     [self sendSettingsChangeNAUWithService:contentNotificationService];
   }
-  [PushNotificationUtil
-      getPermissionSettings:^(UNNotificationSettings* settings) {
-        [PushNotificationUtil
-            updateAuthorizationStatusPref:settings.authorizationStatus];
-      }];
+  [PushNotificationUtil updateAuthorizationStatusPref];
 }
 
 - (void)sendSettingsChangeNAUWithService:
diff --git a/ios/chrome/browser/push_notification/model/push_notification_util.h b/ios/chrome/browser/push_notification/model/push_notification_util.h
index d845690..a7dec54 100644
--- a/ios/chrome/browser/push_notification/model/push_notification_util.h
+++ b/ios/chrome/browser/push_notification/model/push_notification_util.h
@@ -88,6 +88,9 @@
 // status changes on notification permissions.
 + (UNAuthorizationStatus)getSavedPermissionSettings;
 
+// Gets the authorization status from iOS and updates prefs if needed.
++ (void)updateAuthorizationStatusPref;
+
 // This function updates the value stored in the prefService that represents the
 // user's iOS settings permission status for push notifications. If there is a
 // difference between the prefService's previous value and the new value, the
diff --git a/ios/chrome/browser/push_notification/model/push_notification_util.mm b/ios/chrome/browser/push_notification/model/push_notification_util.mm
index cfda6d62..dccf437 100644
--- a/ios/chrome/browser/push_notification/model/push_notification_util.mm
+++ b/ios/chrome/browser/push_notification/model/push_notification_util.mm
@@ -77,6 +77,12 @@
 
 // Key for the client id in the payload.
 NSString* const kClientIdFieldKey = @"n";
+
+// The options to use when requestion notification authorization.
+const UNAuthorizationOptions kAuthorizationOptions =
+    UNAuthorizationOptionAlert | UNAuthorizationOptionBadge |
+    UNAuthorizationOptionSound;
+
 }  // namespace
 
 @implementation PushNotificationUtil
@@ -184,10 +190,14 @@
   }
 }
 
-// This function updates the value stored in the prefService that represents the
-// user's iOS settings permission status for push notifications. If there is a
-// difference between the prefService's previous value and the new value, the
-// change is logged to UMA.
++ (void)updateAuthorizationStatusPref {
+  [PushNotificationUtil
+      getPermissionSettings:^(UNNotificationSettings* settings) {
+        [PushNotificationUtil
+            updateAuthorizationStatusPref:settings.authorizationStatus];
+      }];
+}
+
 + (void)updateAuthorizationStatusPref:(UNAuthorizationStatus)status {
   ApplicationContext* context = GetApplicationContext();
   PrefService* prefService = context->GetLocalState();
@@ -282,12 +292,9 @@
     }
     return;
   }
-  UNAuthorizationOptions options = UNAuthorizationOptionAlert |
-                                   UNAuthorizationOptionBadge |
-                                   UNAuthorizationOptionSound;
   UNUserNotificationCenter* center =
       UNUserNotificationCenter.currentNotificationCenter;
-  [center requestAuthorizationWithOptions:options
+  [center requestAuthorizationWithOptions:kAuthorizationOptions
                         completionHandler:^(BOOL granted, NSError* error) {
                           [PushNotificationUtil
                               requestAuthorizationResult:completion
@@ -312,8 +319,7 @@
     return;
   }
   UNAuthorizationOptions options =
-      UNAuthorizationOptionProvisional | UNAuthorizationOptionBadge |
-      UNAuthorizationOptionAlert | UNAuthorizationOptionSound;
+      kAuthorizationOptions | UNAuthorizationOptionProvisional;
   UNUserNotificationCenter* center =
       UNUserNotificationCenter.currentNotificationCenter;
   [center requestAuthorizationWithOptions:options
@@ -345,6 +351,7 @@
   if (completion) {
     completion(granted, YES, error);
   }
+  [PushNotificationUtil updateAuthorizationStatusPref];
 }
 
 // Reports the push notification permission prompt's outcome to metrics and
@@ -366,6 +373,7 @@
   if (completion) {
     completion(granted, error);
   }
+  [PushNotificationUtil updateAuthorizationStatusPref];
 }
 
 // Logs the permission status, stored in iOS settings, the user has given for
diff --git a/ios/chrome/browser/safety_check_notifications/model/safety_check_notification_client.mm b/ios/chrome/browser/safety_check_notifications/model/safety_check_notification_client.mm
index ec79876..751d803 100644
--- a/ios/chrome/browser/safety_check_notifications/model/safety_check_notification_client.mm
+++ b/ios/chrome/browser/safety_check_notifications/model/safety_check_notification_client.mm
@@ -369,7 +369,7 @@
   UNNotificationRequest* password_notification =
       PasswordNotificationRequest(password_state, insecure_password_counts);
 
-  if (password_notification) {
+  if (password_notification && AreSafetyCheckPasswordsNotificationsAllowed()) {
     [UNUserNotificationCenter.currentNotificationCenter
         addNotificationRequest:password_notification
          withCompletionHandler:nil];
@@ -389,7 +389,8 @@
   UNNotificationRequest* safe_browsing_notification =
       SafeBrowsingNotificationRequest(safe_browsing_state);
 
-  if (safe_browsing_notification) {
+  if (safe_browsing_notification &&
+      AreSafetyCheckSafeBrowsingNotificationsAllowed()) {
     [UNUserNotificationCenter.currentNotificationCenter
         addNotificationRequest:safe_browsing_notification
          withCompletionHandler:nil];
@@ -409,7 +410,8 @@
   UNNotificationRequest* update_chrome_notification =
       UpdateChromeNotificationRequest(update_chrome_state);
 
-  if (update_chrome_notification) {
+  if (update_chrome_notification &&
+      AreSafetyCheckUpdateChromeNotificationsAllowed()) {
     [UNUserNotificationCenter.currentNotificationCenter
         addNotificationRequest:update_chrome_notification
          withCompletionHandler:nil];
diff --git a/ios/chrome/browser/shared/public/features/features.h b/ios/chrome/browser/shared/public/features/features.h
index f7fb76ef..644e0db6 100644
--- a/ios/chrome/browser/shared/public/features/features.h
+++ b/ios/chrome/browser/shared/public/features/features.h
@@ -88,6 +88,18 @@
 // allowed for the Safety Check notifications opt-in button.
 extern const char kSafetyCheckNotificationsImpressionLimit[];
 
+// Name of the parameter that controls whether Passwords notifications
+// are permitted to be sent to the user for Safety Check.
+extern const char kSafetyCheckAllowPasswordsNotifications[];
+
+// Name of the parameter that controls whether Safe Browsing notifications
+// are permitted to be sent to the user for Safety Check.
+extern const char kSafetyCheckAllowSafeBrowsingNotifications[];
+
+// Name of the parameter that controls whether Update Chrome notifications
+// are permitted to be sent to the user for Safety Check.
+extern const char kSafetyCheckAllowUpdateChromeNotifications[];
+
 // Defines param values for the Safety Check Notifications feature,
 // controlling how notifications are presented to the user.
 enum class SafetyCheckNotificationsExperimentalArm {
@@ -111,6 +123,21 @@
 // Feature flag to add the Tips module to the Magic Stack.
 BASE_DECLARE_FEATURE(kTipsMagicStack);
 
+// Name of the parameter that controls the experiment type for the Lens Shop
+// tip, determining whether or not a product image is displayed.
+extern const char kTipsLensShopExperimentType[];
+
+// Defines the different experiment arms for the Lens Shop tip, which
+// determine whether or not a product image is displayed (if available).
+enum class TipsLensShopExperimentType {
+  // The experiment arm that shows the product image (if available) in the
+  // Lens Shop tip.
+  kWithProductImage = 0,
+  // The experiment arm that does not show the product image in the Lens shop
+  // tip.
+  kWithoutProductImage = 1,
+};
+
 // Feature flag to enable Shared Highlighting (Link to Text).
 BASE_DECLARE_FEATURE(kSharedHighlightingIOS);
 
@@ -354,9 +381,28 @@
 // Whether Safety Check Push Notifications should be sent to the user.
 bool IsSafetyCheckNotificationsEnabled();
 
+// Checks if Passwords notifications are permitted to be sent to the user
+// for Safety Check, based on the Finch parameter
+// `kSafetyCheckAllowPasswordsNotifications`.
+bool AreSafetyCheckPasswordsNotificationsAllowed();
+
+// Checks if Safe Browsing notifications are permitted to be sent to the user
+// for Safety Check, based on the Finch parameter
+// `kSafetyCheckAllowSafeBrowsingNotifications`.
+bool AreSafetyCheckSafeBrowsingNotificationsAllowed();
+
+// Checks if Update Chrome notifications are permitted to be sent to the user
+// for Safety Check, based on the Finch parameter
+// `kSafetyCheckAllowUpdateChromeNotifications`.
+bool AreSafetyCheckUpdateChromeNotificationsAllowed();
+
 // Whether the Tips module should be shown in the Magic Stack.
 bool IsTipsMagicStackEnabled();
 
+// Returns the experiment type for the Lens Shop tip, which determines
+// whether or not a product image is displayed (if available).
+TipsLensShopExperimentType TipsLensShopExperimentTypeEnabled();
+
 // Whether the refactored implementation of the `OmahaService` is enabled.
 bool IsOmahaServiceRefactorEnabled();
 
diff --git a/ios/chrome/browser/shared/public/features/features.mm b/ios/chrome/browser/shared/public/features/features.mm
index 8a27df7..8fba3fc 100644
--- a/ios/chrome/browser/shared/public/features/features.mm
+++ b/ios/chrome/browser/shared/public/features/features.mm
@@ -61,6 +61,8 @@
              "TipsMagicStack",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+const char kTipsLensShopExperimentType[] = "TipsLensShopExperimentType";
+
 const char kSafetyCheckNotificationsExperimentType[] =
     "SafetyCheckNotificationsExperimentType";
 
@@ -70,6 +72,15 @@
 const char kSafetyCheckNotificationsImpressionLimit[] =
     "SafetyCheckNotificationsImpressionLimit";
 
+const char kSafetyCheckAllowPasswordsNotifications[] =
+    "SafetyCheckAllowPasswordsNotifications";
+
+const char kSafetyCheckAllowSafeBrowsingNotifications[] =
+    "SafetyCheckAllowSafeBrowsingNotifications";
+
+const char kSafetyCheckAllowUpdateChromeNotifications[] =
+    "SafetyCheckAllowUpdateChromeNotifications";
+
 const char kSafetyCheckMagicStackAutorunHoursThreshold[] =
     "SafetyCheckMagicStackAutorunHoursThreshold";
 
@@ -79,6 +90,30 @@
 const char kSafetyCheckNotificationsUserInactiveThreshold[] =
     "SafetyCheckNotificationsUserInactiveThreshold";
 
+// This helper should return true by default, as this parameter primarily serves
+// as a killswitch.
+bool AreSafetyCheckPasswordsNotificationsAllowed() {
+  return base::GetFieldTrialParamByFeatureAsBool(
+      kSafetyCheckNotifications, kSafetyCheckAllowPasswordsNotifications,
+      /*default_value=*/true);
+}
+
+// This helper should return true by default, as this parameter primarily serves
+// as a killswitch.
+bool AreSafetyCheckSafeBrowsingNotificationsAllowed() {
+  return base::GetFieldTrialParamByFeatureAsBool(
+      kSafetyCheckNotifications, kSafetyCheckAllowSafeBrowsingNotifications,
+      /*default_value=*/true);
+}
+
+// This helper should return true by default, as this parameter primarily serves
+// as a killswitch.
+bool AreSafetyCheckUpdateChromeNotificationsAllowed() {
+  return base::GetFieldTrialParamByFeatureAsBool(
+      kSafetyCheckNotifications, kSafetyCheckAllowUpdateChromeNotifications,
+      /*default_value=*/true);
+}
+
 bool ProvisionalSafetyCheckNotificationsEnabled() {
   return base::GetFieldTrialParamByFeatureAsBool(
       kSafetyCheckNotifications, kSafetyCheckNotificationsProvisionalEnabled,
@@ -340,7 +375,16 @@
 }
 
 bool IsTipsMagicStackEnabled() {
-  return base::FeatureList::IsEnabled(kTipsMagicStack);
+  return IsSegmentationTipsManagerEnabled() &&
+         base::FeatureList::IsEnabled(kTipsMagicStack);
+}
+
+TipsLensShopExperimentType TipsLensShopExperimentTypeEnabled() {
+  return static_cast<TipsLensShopExperimentType>(
+      base::GetFieldTrialParamByFeatureAsInt(
+          kTipsMagicStack, kTipsLensShopExperimentType,
+          /*default_value=*/
+          (int)TipsLensShopExperimentType::kWithoutProductImage));
 }
 
 BASE_FEATURE(kIOSChooseFromDrive,
diff --git a/ios/chrome/browser/ui/content_suggestions/BUILD.gn b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
index a9d3ca4..4aacfd42 100644
--- a/ios/chrome/browser/ui/content_suggestions/BUILD.gn
+++ b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
@@ -30,6 +30,7 @@
     "//components/prefs",
     "//components/search_engines",
     "//components/segmentation_platform/embedder/home_modules",
+    "//components/segmentation_platform/embedder/home_modules/tips_manager:constants",
     "//components/segmentation_platform/public",
     "//components/strings",
     "//components/sync/base:features",
@@ -104,6 +105,7 @@
     "//ios/chrome/browser/ui/content_suggestions/set_up_list:default_browser_promo",
     "//ios/chrome/browser/ui/content_suggestions/set_up_list:utils",
     "//ios/chrome/browser/ui/content_suggestions/tab_resumption",
+    "//ios/chrome/browser/ui/content_suggestions/tips",
     "//ios/chrome/browser/ui/menu",
     "//ios/chrome/browser/ui/push_notification:notifications_opt_in",
     "//ios/chrome/browser/ui/push_notification:opt_in_alert_coordinator",
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h
index db74228..5ae15a4 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h
@@ -32,7 +32,14 @@
   kSetUpListNotifications = 13,
   kPlaceholder = 14,
   kPriceTrackingPromo = 15,
-  kMaxValue = kPriceTrackingPromo,
+  // Larger variant of `kTips` with different layout/formatting for displaying
+  // larger-sized product images within the module.
+  //
+  // TODO(crbug.com/370479820): Deprecate when Magic Stack supports dynamic
+  // styling and layout decoupled from `ContentSuggestionsModuleType`.
+  kTipsWithProductImage = 16,
+  kTips = 17,
+  kMaxValue = kTips,
 };
 // LINT.ThenChange(/tools/metrics/histograms/metadata/ios/enums.xml)
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.mm
index daf49fd5..0003cbc 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.mm
@@ -65,6 +65,8 @@
     case ContentSuggestionsModuleType::kShortcuts:
     case ContentSuggestionsModuleType::kPlaceholder:
     case ContentSuggestionsModuleType::kPriceTrackingPromo:
+    case ContentSuggestionsModuleType::kTipsWithProductImage:
+    case ContentSuggestionsModuleType::kTips:
       return false;
     case ContentSuggestionsModuleType::kSetUpListSync:
     case ContentSuggestionsModuleType::kSetUpListDefaultBrowser:
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
index cbb8470..cf20c2d 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
@@ -26,6 +26,7 @@
 #import "components/search_engines/template_url_service.h"
 #import "components/segmentation_platform/embedder/home_modules/constants.h"
 #import "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h"
+#import "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h"
 #import "components/segmentation_platform/public/features.h"
 #import "components/segmentation_platform/public/segmentation_platform_service.h"
 #import "ios/chrome/app/application_delegate/app_state.h"
@@ -132,6 +133,7 @@
 #import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_tap_delegate.h"
 #import "ios/chrome/browser/ui/content_suggestions/set_up_list/utils.h"
 #import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_mediator.h"
+#import "ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator.h"
 #import "ios/chrome/browser/ui/menu/browser_action_factory.h"
 #import "ios/chrome/browser/ui/menu/menu_histograms.h"
 #import "ios/chrome/browser/ui/push_notification/notifications_confirmation_presenter.h"
@@ -148,6 +150,8 @@
 #import "ui/base/l10n/l10n_util_mac.h"
 #import "url/gurl.h"
 
+using segmentation_platform::TipIdentifier;
+
 @interface ContentSuggestionsCoordinator () <
     ContentSuggestionsCommands,
     ContentSuggestionsViewControllerAudience,
@@ -227,6 +231,7 @@
   // Module mediators.
   ShortcutsMediator* _shortcutsMediator;
   SafetyCheckMagicStackMediator* _safetyCheckMediator;
+  TipsMagicStackMediator* _tipsMediator;
   MostVisitedTilesMediator* _mostVisitedTilesMediator;
   TabResumptionMediator* _tabResumptionMediator;
   PriceTrackingPromoMediator* _priceTrackingPromoMediator;
@@ -410,6 +415,13 @@
     _safetyCheckMediator.presentationAudience = self;
     [moduleMediators addObject:_safetyCheckMediator];
   }
+
+  if (IsTipsMagicStackEnabled()) {
+    _tipsMediator = [[TipsMagicStackMediator alloc]
+        initWithIdentifier:TipIdentifier::kUnknown];
+    [moduleMediators addObject:_tipsMediator];
+  }
+
   if (!ShouldPutMostVisitedSitesInMagicStack()) {
     ContentSuggestionsViewController* viewController =
         [[ContentSuggestionsViewController alloc] init];
@@ -486,6 +498,7 @@
   _shortcutsMediator = nil;
   [_safetyCheckMediator disconnect];
   _safetyCheckMediator = nil;
+  _tipsMediator = nil;
   [_setUpListMediator disconnect];
   _setUpListMediator = nil;
   [_mostVisitedTilesMediator disconnect];
@@ -624,6 +637,12 @@
       registry->NotifyCardShown(
           segmentation_platform::kPriceTrackingNotificationPromo);
       break;
+    case ContentSuggestionsModuleType::kTipsWithProductImage:
+    case ContentSuggestionsModuleType::kTips:
+      // TODO(crbug.com/370484933): Integrate the Tips (Magic Stack) module with
+      // the Ephemeral module infrastructure. Once integrated, notify the
+      // registry that the Tips card was shown.
+      break;
     default:
       NOTREACHED();
   }
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_constants.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_constants.h
index e13b9631..5fe5f0c 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_constants.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_constants.h
@@ -43,6 +43,7 @@
 extern const char kMagicStackModuleEngagementSafetyCheckIndexHistogram[];
 extern const char kMagicStackModuleEngagementParcelTrackingIndexHistogram[];
 extern const char kMagicStackModuleEngagementPriceTrackingPromoIndexHistogram[];
+extern const char kMagicStackModuleEngagementTipsIndexHistogram[];
 extern const char kMagicStackModuleDisabledHistogram[];
 extern const char kContentNotificationSnackbarEventHistogram[];
 extern const char kIOSSafetyCheckMagicStackHiddenReason[];
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_constants.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_constants.mm
index 3803f67..65eedb7 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_constants.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_constants.mm
@@ -39,6 +39,8 @@
     "IOS.MagicStack.Module.Click.ParcelTracking";
 const char kMagicStackModuleEngagementPriceTrackingPromoIndexHistogram[] =
     "IOS.MagicStack.Module.Click.PriceTrackingPromo";
+const char kMagicStackModuleEngagementTipsIndexHistogram[] =
+    "IOS.MagicStack.Module.Click.Tips";
 const char kMagicStackModuleDisabledHistogram[] =
     "IOS.MagicStack.Module.Disabled";
 const char kContentNotificationSnackbarEventHistogram[] =
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_recorder.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_recorder.mm
index d990dfe..9ec9a86 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_recorder.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_recorder.mm
@@ -94,6 +94,11 @@
           kMagicStackModuleEngagementSetUpListIndexHistogram, index,
           kMaxModuleEngagementIndex);
       break;
+    case ContentSuggestionsModuleType::kTipsWithProductImage:
+    case ContentSuggestionsModuleType::kTips:
+      UMA_HISTOGRAM_EXACT_LINEAR(kMagicStackModuleEngagementTipsIndexHistogram,
+                                 index, kMaxModuleEngagementIndex);
+      break;
     case ContentSuggestionsModuleType::kPlaceholder:
     case ContentSuggestionsModuleType::kInvalid:
       break;
diff --git a/ios/chrome/browser/ui/content_suggestions/magic_stack/BUILD.gn b/ios/chrome/browser/ui/content_suggestions/magic_stack/BUILD.gn
index a896a3fe..ca41a69 100644
--- a/ios/chrome/browser/ui/content_suggestions/magic_stack/BUILD.gn
+++ b/ios/chrome/browser/ui/content_suggestions/magic_stack/BUILD.gn
@@ -33,6 +33,7 @@
     "//components/commerce/core:shopping_service",
     "//components/prefs",
     "//components/segmentation_platform/embedder/home_modules",
+    "//components/segmentation_platform/embedder/home_modules/tips_manager:constants",
     "//components/segmentation_platform/public",
     "//ios/chrome/browser/ntp/ui_bundled",
     "//ios/chrome/browser/ntp_tiles/model/tab_resumption:tab_resumption_prefs",
@@ -42,6 +43,7 @@
     "//ios/chrome/browser/shared/model/prefs:pref_names",
     "//ios/chrome/browser/shared/model/utils",
     "//ios/chrome/browser/shared/public/features",
+    "//ios/chrome/browser/shared/public/features:system_flags",
     "//ios/chrome/browser/ui/content_suggestions:constants",
     "//ios/chrome/browser/ui/content_suggestions:metrics",
     "//ios/chrome/browser/ui/content_suggestions:public",
@@ -54,6 +56,7 @@
     "//ios/chrome/browser/ui/content_suggestions/set_up_list",
     "//ios/chrome/browser/ui/content_suggestions/set_up_list:utils",
     "//ios/chrome/browser/ui/content_suggestions/tab_resumption",
+    "//ios/chrome/browser/ui/content_suggestions/tips",
   ]
   frameworks = [ "UIKit.framework" ]
 }
@@ -123,6 +126,7 @@
     "//ios/chrome/browser/ui/content_suggestions/set_up_list",
     "//ios/chrome/browser/ui/content_suggestions/set_up_list:utils",
     "//ios/chrome/browser/ui/content_suggestions/tab_resumption",
+    "//ios/chrome/browser/ui/content_suggestions/tips",
     "//ios/chrome/common/ui/colors",
     "//ios/chrome/common/ui/favicon",
     "//ios/chrome/common/ui/util",
diff --git a/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_collection_view.mm b/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_collection_view.mm
index 13a82c5..33bef33 100644
--- a/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_collection_view.mm
+++ b/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_collection_view.mm
@@ -428,6 +428,8 @@
 - (BOOL)isCardEphemeral:(MagicStackModule*)card {
   switch (card.type) {
     case ContentSuggestionsModuleType::kPriceTrackingPromo:
+    case ContentSuggestionsModuleType::kTipsWithProductImage:
+    case ContentSuggestionsModuleType::kTips:
       return YES;
     case ContentSuggestionsModuleType::kMostVisited:
     case ContentSuggestionsModuleType::kShortcuts:
diff --git a/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_module_container.mm b/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_module_container.mm
index 9e4ed0c..67d3cda 100644
--- a/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_module_container.mm
+++ b/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_module_container.mm
@@ -376,6 +376,9 @@
     case ContentSuggestionsModuleType::kPriceTrackingPromo:
       // Price Tracking Promo design does not use title.
       return @"";
+    case ContentSuggestionsModuleType::kTipsWithProductImage:
+    case ContentSuggestionsModuleType::kTips:
+      return l10n_util::GetNSString(IDS_IOS_MAGIC_STACK_TIP_TITLE);
     default:
       NOTREACHED_IN_MIGRATION();
       return @"";
@@ -498,9 +501,12 @@
     case ContentSuggestionsModuleType::kSetUpListAllSet:
     case ContentSuggestionsModuleType::kSetUpListNotifications:
     case ContentSuggestionsModuleType::kSafetyCheck:
+    case ContentSuggestionsModuleType::kTips:
       return YES;
     case ContentSuggestionsModuleType::kTabResumption:
       return !IsTabResumption1_5Enabled();
+    case ContentSuggestionsModuleType::kTipsWithProductImage:
+      return NO;
     default:
       return NO;
   }
diff --git a/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_module_contents_factory.mm b/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_module_contents_factory.mm
index 3fa94572..de37f44 100644
--- a/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_module_contents_factory.mm
+++ b/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_module_contents_factory.mm
@@ -29,6 +29,8 @@
 #import "ios/chrome/browser/ui/content_suggestions/set_up_list/utils.h"
 #import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_item.h"
 #import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_view.h"
+#import "ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.h"
+#import "ios/chrome/browser/ui/content_suggestions/tips/tips_module_view.h"
 
 @implementation MagicStackModuleContentsFactory
 
@@ -82,6 +84,11 @@
       SetUpListConfig* setUpListConfig = static_cast<SetUpListConfig*>(config);
       return [self setUpListViewForConfig:setUpListConfig];
     }
+    case ContentSuggestionsModuleType::kTipsWithProductImage:
+    case ContentSuggestionsModuleType::kTips: {
+      TipsModuleState* tipsConfig = static_cast<TipsModuleState*>(config);
+      return [self tipsViewForConfig:tipsConfig];
+    }
     default:
       NOTREACHED();
   }
@@ -179,4 +186,10 @@
   return [[MultiRowContainerView alloc] initWithViews:compactedSetUpListViews];
 }
 
+- (UIView*)tipsViewForConfig:(TipsModuleState*)state {
+  TipsModuleView* view = [[TipsModuleView alloc] initWithState:state];
+
+  return view;
+}
+
 @end
diff --git a/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_ranking_model.mm b/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_ranking_model.mm
index 1a71db89..0ded88f7 100644
--- a/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_ranking_model.mm
+++ b/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_ranking_model.mm
@@ -4,6 +4,8 @@
 
 #import "ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_ranking_model.h"
 
+#import <optional>
+
 #import "base/memory/raw_ptr.h"
 #import "base/metrics/histogram_functions.h"
 #import "base/metrics/histogram_macros.h"
@@ -11,6 +13,7 @@
 #import "components/commerce/core/shopping_service.h"
 #import "components/prefs/pref_service.h"
 #import "components/segmentation_platform/embedder/home_modules/constants.h"
+#import "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h"
 #import "components/segmentation_platform/public/constants.h"
 #import "components/segmentation_platform/public/features.h"
 #import "components/segmentation_platform/public/segmentation_platform_service.h"
@@ -23,6 +26,7 @@
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
 #import "ios/chrome/browser/shared/model/utils/first_run_util.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
+#import "ios/chrome/browser/shared/public/features/system_flags.h"
 #import "ios/chrome/browser/ui/content_suggestions/cells/most_visited_tiles_config.h"
 #import "ios/chrome/browser/ui/content_suggestions/cells/most_visited_tiles_mediator.h"
 #import "ios/chrome/browser/ui/content_suggestions/cells/shortcuts_config.h"
@@ -46,6 +50,10 @@
 #import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_helper_delegate.h"
 #import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_item.h"
 #import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_mediator.h"
+#import "ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator.h"
+#import "ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.h"
+
+using segmentation_platform::TipIdentifier;
 
 @interface MagicStackRankingModel () <MostVisitedTilesMediatorDelegate,
                                       ParcelTrackingMediatorDelegate,
@@ -82,6 +90,7 @@
   PriceTrackingPromoMediator* _priceTrackingPromoMediator;
   ShortcutsMediator* _shortcutsMediator;
   SafetyCheckMagicStackMediator* _safetyCheckMediator;
+  TipsMagicStackMediator* _tipsMediator;
   base::TimeTicks ranking_fetch_start_time_;
   ContentSuggestionsModuleType _ephemeralCardToShow;
 }
@@ -130,6 +139,8 @@
         _safetyCheckMediator =
             static_cast<SafetyCheckMagicStackMediator*>(mediator);
         _safetyCheckMediator.delegate = self;
+      } else if ([mediator isKindOfClass:[TipsMagicStackMediator class]]) {
+        _tipsMediator = static_cast<TipsMagicStackMediator*>(mediator);
       } else {
         // Known module mediators need to be handled.
         NOTREACHED_IN_MIGRATION();
@@ -147,6 +158,7 @@
   _priceTrackingPromoMediator = nil;
   _shortcutsMediator = nil;
   _safetyCheckMediator = nil;
+  _tipsMediator = nil;
 }
 
 #pragma mark - Public
@@ -354,6 +366,7 @@
   }
 
   MagicStackModule* card;
+
   for (const std::string& label : result.ordered_labels) {
     if (label == segmentation_platform::kPriceTrackingNotificationPromo) {
       if (IsPriceTrackingPromoCardEnabled(_shoppingService, _authService,
@@ -364,6 +377,43 @@
         break;
       }
     }
+
+    // TODO(crbug.com/370484933): Integrate the Tips (Magic Stack) module with
+    // the Ephemeral module infrastructure. Once integrated, use the Ephemeral
+    // module's label targeting to determine which tip variation to show.
+    if (IsTipsMagicStackEnabled()) {
+      // Check if a forced tip state is set through Experimental Settings.
+      std::optional<int> forcedTipState =
+          experimental_flags::GetForcedTipsMagicStackState();
+
+      // Convert the forced tip state to a `TipIdentifier`, defaulting to
+      // `kUnknown` if no forced state is set.
+      TipIdentifier tipIdentifier =
+          static_cast<TipIdentifier>(forcedTipState.value_or(0));
+
+      // If not `kUnknown`, a forced tip state is active. Reconfigure the Tips
+      // mediator and module with the new forced tip, and add it to the Magic
+      // Stack.
+      if (tipIdentifier != TipIdentifier::kUnknown) {
+        // Check if the 'TipsMagicStackLensShopWithImage' experimental override
+        // is set to determine whether to display a product image inside the
+        // Lens Shop tip.
+        BOOL displayLensShopWithImage =
+            tipIdentifier == TipIdentifier::kLensShop &&
+            experimental_flags::ShouldDisplayLensShopTipWithImage();
+
+        _ephemeralCardToShow =
+            displayLensShopWithImage
+                ? ContentSuggestionsModuleType::kTipsWithProductImage
+                : ContentSuggestionsModuleType::kTips;
+
+        [_tipsMediator reconfigureWithTipIdentifier:tipIdentifier];
+
+        card = _tipsMediator.state;
+
+        break;
+      }
+    }
   }
   if (_ephemeralCardToShow != ContentSuggestionsModuleType::kInvalid) {
     if (!card) {
@@ -537,6 +587,13 @@
                                          .priceTrackingPromoItemToShow];
         }
         break;
+      case ContentSuggestionsModuleType::kTips:
+      case ContentSuggestionsModuleType::kTipsWithProductImage: {
+        if (IsTipsMagicStackEnabled()) {
+          [magicStackOrder addObject:_tipsMediator.state];
+        }
+        break;
+      }
       default:
         break;
     }
diff --git a/ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_mediator.mm b/ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_mediator.mm
index 9d277a4..e4628a3 100644
--- a/ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_mediator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_mediator.mm
@@ -182,15 +182,15 @@
 
 - (void)onPriceTrackedBookarksReceived:
     (std::vector<const bookmarks::BookmarkNode*>)subscriptions {
-  const power_bookmarks::ShoppingSpecifics* most_recent =
-      [self getMostRecentBookmarkSpecifics:subscriptions];
-  if (!most_recent) {
+  if (subscriptions.empty()) {
     return;
   }
+  GURL most_recent_subscription_product_url =
+      [self getMostRecentSubscriptionProductUrl:subscriptions];
   __weak PriceTrackingPromoMediator* weakSelf = self;
   // There is a subscription but no image url - the price tracking promo
   // will be displayed but with the fallback image.
-  if (!most_recent->has_image_url()) {
+  if (most_recent_subscription_product_url.is_empty()) {
     _priceTrackingPromoItem = [[PriceTrackingPromoItem alloc] init];
     _priceTrackingPromoItem.commandHandler = self;
     [self.delegate newSubscriptionAvailable];
@@ -198,7 +198,7 @@
     // If we have an image, fetch it and display the price tracking promo
     // with that image.
     _imageFetcher->FetchImageData(
-        GURL(most_recent->image_url()),
+        most_recent_subscription_product_url,
         base::BindOnce(^(const std::string& imageData,
                          const image_fetcher::RequestMetadata& metadata) {
           PriceTrackingPromoMediator* strongSelf = weakSelf;
@@ -211,9 +211,10 @@
   }
 }
 
-- (const power_bookmarks::ShoppingSpecifics*)getMostRecentBookmarkSpecifics:
+- (GURL)getMostRecentSubscriptionProductUrl:
     (std::vector<const bookmarks::BookmarkNode*>)subscriptions {
-  const power_bookmarks::ShoppingSpecifics* most_recent = nil;
+  GURL most_recent_subscription_product_url;
+  int64_t most_recent_subscription_time = 0;
   for (const bookmarks::BookmarkNode* bookmark : subscriptions) {
     std::unique_ptr<power_bookmarks::PowerBookmarkMeta> meta =
         power_bookmarks::GetNodePowerBookmarkMeta(_bookmarkModel, bookmark);
@@ -222,12 +223,14 @@
     }
     const power_bookmarks::ShoppingSpecifics specifics =
         meta->shopping_specifics();
-    if (!most_recent || specifics.last_subscription_change_time() >
-                            most_recent->last_subscription_change_time()) {
-      most_recent = &specifics;
+    if (most_recent_subscription_product_url.is_empty() ||
+        most_recent_subscription_time <
+            specifics.last_subscription_change_time()) {
+      most_recent_subscription_product_url = GURL(meta->lead_image().url());
+      most_recent_subscription_time = specifics.last_subscription_change_time();
     }
   }
-  return most_recent;
+  return most_recent_subscription_product_url;
 }
 
 - (void)onImageFetchedResult:(const std::string&)imageData {
diff --git a/ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_view.mm b/ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_view.mm
index 18662e08..26c1c97 100644
--- a/ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_view.mm
+++ b/ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_view.mm
@@ -27,6 +27,9 @@
 // Inset for product image fallback from the UIImageView boundary.
 const CGFloat kProductImageFallbackInset = 10.0f;
 
+// Inset for product image from the UIImageView boundary.
+const CGFloat kProductImageInset = 0.0f;
+
 // Radius of background circle of product image fallback.
 const CGFloat kProductImageFallbackCornerRadius = 25.0;
 
@@ -45,9 +48,10 @@
   UILabel* _titleLabel;
   UILabel* _descriptionLabel;
   UIButton* _allowButton;
-  UIImageView* _fallbackProductImageView;
+  UIImageView* _productImageView;
   // To create a background circle around the fallback product image.
-  UIView* _fallbackProductImageBackgroundCircle;
+  UIImageView* _fallbackProductImageView;
+  UIView* _productImage;
   UIStackView* _contentStack;
   UIStackView* _textStack;
   UIView* _separator;
@@ -96,31 +100,43 @@
 
   // TODO(crbug.com/361106168) use product image from most recent subscription
   // if available.
-  _fallbackProductImageView = [[UIImageView alloc] init];
-  _fallbackProductImageView.image = CustomSymbolWithPointSize(
-      kDownTrendSymbol, kProductImageFallbackPointSize);
-  _fallbackProductImageView.contentMode = UIViewContentModeScaleAspectFit;
-  _fallbackProductImageView.translatesAutoresizingMaskIntoConstraints = NO;
-  _fallbackProductImageView.layer.borderWidth = 0;
 
-  [NSLayoutConstraint activateConstraints:@[
-    [_fallbackProductImageView.widthAnchor
-        constraintEqualToConstant:kProductImageFallbackSize],
-    [_fallbackProductImageView.widthAnchor
-        constraintEqualToAnchor:_fallbackProductImageView.heightAnchor],
-  ]];
+  _productImage = [[UIView alloc] init];
+  UIImage* retrievedProductImage =
+      [UIImage imageWithData:config.productImageData
+                       scale:[UIScreen mainScreen].scale];
+  if (retrievedProductImage) {
+    _productImageView = [[UIImageView alloc] init];
+    _productImageView.image = retrievedProductImage;
+    _productImageView.contentMode = UIViewContentModeScaleAspectFit;
+    _productImageView.translatesAutoresizingMaskIntoConstraints = NO;
+    _productImageView.layer.borderWidth = 0;
+    [_productImage addSubview:_productImageView];
+    AddSameConstraintsWithInset(_productImageView, _productImage,
+                                kProductImageInset);
+  } else {
+    _fallbackProductImageView = [[UIImageView alloc] init];
+    _fallbackProductImageView.image = CustomSymbolWithPointSize(
+        kDownTrendSymbol, kProductImageFallbackPointSize);
+    _fallbackProductImageView.contentMode = UIViewContentModeScaleAspectFit;
+    _fallbackProductImageView.translatesAutoresizingMaskIntoConstraints = NO;
+    _fallbackProductImageView.layer.borderWidth = 0;
 
-  _fallbackProductImageBackgroundCircle = [[UIView alloc] init];
-  _fallbackProductImageBackgroundCircle.layer.cornerRadius =
-      kProductImageFallbackCornerRadius;
-  _fallbackProductImageBackgroundCircle.backgroundColor =
-      [UIColor colorNamed:kBlueHaloColor];
+    [NSLayoutConstraint activateConstraints:@[
+      [_fallbackProductImageView.widthAnchor
+          constraintEqualToConstant:kProductImageFallbackSize],
+      [_fallbackProductImageView.widthAnchor
+          constraintEqualToAnchor:_fallbackProductImageView.heightAnchor],
+    ]];
 
-  [_fallbackProductImageBackgroundCircle addSubview:_fallbackProductImageView];
+    _productImage.layer.cornerRadius = kProductImageFallbackCornerRadius;
+    _productImage.backgroundColor = [UIColor colorNamed:kBlueHaloColor];
 
-  AddSameConstraintsWithInset(_fallbackProductImageView,
-                              _fallbackProductImageBackgroundCircle,
-                              kProductImageFallbackInset);
+    [_productImage addSubview:_fallbackProductImageView];
+
+    AddSameConstraintsWithInset(_fallbackProductImageView, _productImage,
+                                kProductImageFallbackInset);
+  }
 
   _allowButton = [[UIButton alloc] init];
   _allowButton.translatesAutoresizingMaskIntoConstraints = NO;
@@ -157,9 +173,8 @@
         constraintEqualToAnchor:_textStack.trailingAnchor],
   ]];
 
-  _contentStack = [[UIStackView alloc] initWithArrangedSubviews:@[
-    _fallbackProductImageBackgroundCircle, _textStack
-  ]];
+  _contentStack = [[UIStackView alloc]
+      initWithArrangedSubviews:@[ _productImage, _textStack ]];
   _contentStack.translatesAutoresizingMaskIntoConstraints = NO;
   _contentStack.spacing = kHorizontalStackSpacing;
   _contentStack.alignment = UIStackViewAlignmentTop;
diff --git a/ios/chrome/browser/ui/content_suggestions/tips/BUILD.gn b/ios/chrome/browser/ui/content_suggestions/tips/BUILD.gn
new file mode 100644
index 0000000..318e92f
--- /dev/null
+++ b/ios/chrome/browser/ui/content_suggestions/tips/BUILD.gn
@@ -0,0 +1,46 @@
+# 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.
+
+source_set("tips") {
+  sources = [
+    "tips_magic_stack_mediator.h",
+    "tips_magic_stack_mediator.mm",
+    "tips_module_state.h",
+    "tips_module_state.mm",
+    "tips_module_view.h",
+    "tips_module_view.mm",
+  ]
+  public_deps =
+      [ "//ios/chrome/browser/ui/content_suggestions/magic_stack:public" ]
+  deps = [
+    "//base",
+    "//components/segmentation_platform/embedder/home_modules/tips_manager:constants",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser/shared/ui/symbols",
+    "//ios/chrome/browser/ui/content_suggestions:constants",
+    "//ios/chrome/browser/ui/content_suggestions/cells",
+    "//ios/chrome/browser/ui/content_suggestions/magic_stack:public",
+    "//ios/chrome/common/ui/util",
+    "//ui/base",
+  ]
+  frameworks = [ "UIKit.framework" ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "tips_magic_stack_mediator_unittest.mm",
+    "tips_module_view_unittest.mm",
+  ]
+  deps = [
+    ":tips",
+    "//base",
+    "//base/test:test_support",
+    "//components/segmentation_platform/embedder/home_modules/tips_manager:constants",
+    "//ios/chrome/browser/ui/content_suggestions:public",
+    "//ios/chrome/browser/ui/content_suggestions/cells",
+    "//testing/gtest",
+  ]
+  frameworks = [ "Foundation.framework" ]
+}
diff --git a/ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator.h b/ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator.h
new file mode 100644
index 0000000..a3aa6b02
--- /dev/null
+++ b/ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator.h
@@ -0,0 +1,33 @@
+// 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 IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_TIPS_TIPS_MAGIC_STACK_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_TIPS_TIPS_MAGIC_STACK_MEDIATOR_H_
+
+#import <UIKit/UIKit.h>
+
+@class TipsModuleState;
+namespace segmentation_platform {
+enum class TipIdentifier;
+}  // namespace segmentation_platform
+
+// Mediator for managing the state of the Tips (Magic Stack) module.
+@interface TipsMagicStackMediator : NSObject
+
+// Used by the Tips module for the current module state.
+@property(nonatomic, strong, readonly) TipsModuleState* state;
+
+// Default initializer.
+- (instancetype)initWithIdentifier:
+    (segmentation_platform::TipIdentifier)identifier NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+// Reconfigures `TipsMagicStackMediator` with a new tip `identifier`.
+- (void)reconfigureWithTipIdentifier:
+    (segmentation_platform::TipIdentifier)identifier;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_TIPS_TIPS_MAGIC_STACK_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator.mm b/ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator.mm
new file mode 100644
index 0000000..47ce160
--- /dev/null
+++ b/ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator.mm
@@ -0,0 +1,28 @@
+// 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/ui/content_suggestions/tips/tips_magic_stack_mediator.h"
+
+#import "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h"
+#import "ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.h"
+
+using segmentation_platform::TipIdentifier;
+
+@implementation TipsMagicStackMediator
+
+- (instancetype)initWithIdentifier:(TipIdentifier)identifier {
+  self = [super init];
+
+  if (self) {
+    _state = [[TipsModuleState alloc] initWithIdentifier:identifier];
+  }
+
+  return self;
+}
+
+- (void)reconfigureWithTipIdentifier:(TipIdentifier)identifier {
+  _state = [[TipsModuleState alloc] initWithIdentifier:identifier];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator_unittest.mm b/ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator_unittest.mm
new file mode 100644
index 0000000..bbfdc491
--- /dev/null
+++ b/ios/chrome/browser/ui/content_suggestions/tips/tips_magic_stack_mediator_unittest.mm
@@ -0,0 +1,43 @@
+// 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//ui/content_suggestions/tips/tips_magic_stack_mediator.h"
+
+#import <Foundation/Foundation.h>
+
+#import "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h"
+#import "ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.h"
+#import "testing/gtest_mac.h"
+#import "testing/platform_test.h"
+
+using segmentation_platform::TipIdentifier;
+
+// Tests the `TipsMagicStackMediator`.
+class TipsMagicStackMediatorTest : public PlatformTest {
+ public:
+  void SetUp() override {
+    // Create a `TipsMagicStackMediator` with an initial unknown
+    // `TipIdentifier`.
+    mediator_ = [[TipsMagicStackMediator alloc]
+        initWithIdentifier:TipIdentifier::kUnknown];
+  }
+
+  void TearDown() override { mediator_ = nil; }
+
+ protected:
+  TipsMagicStackMediator* mediator_;
+};
+
+// Tests that the mediator's initial state is configured correctly for an
+// unknown tip.
+TEST_F(TipsMagicStackMediatorTest, HasCorrectInitialStateForUnknownTip) {
+  EXPECT_EQ(TipIdentifier::kUnknown, mediator_.state.identifier);
+}
+
+// Tests that the mediator reconfigures its state to reflect a new tip.
+TEST_F(TipsMagicStackMediatorTest, ReconfiguresStateForNewTip) {
+  [mediator_ reconfigureWithTipIdentifier:TipIdentifier::kLensShop];
+
+  EXPECT_EQ(TipIdentifier::kLensShop, mediator_.state.identifier);
+}
diff --git a/ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.h b/ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.h
new file mode 100644
index 0000000..79ad8b54
--- /dev/null
+++ b/ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.h
@@ -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.
+
+#ifndef IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_TIPS_TIPS_MODULE_STATE_H_
+#define IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_TIPS_TIPS_MODULE_STATE_H_
+
+#import "ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_module.h"
+
+namespace segmentation_platform {
+enum class TipIdentifier;
+}  // namespace segmentation_platform
+
+// Helper class to contain the current Tips module state.
+@interface TipsModuleState : MagicStackModule
+
+// Initializes a `TipsModuleState` with `identifier`.
+- (instancetype)initWithIdentifier:
+    (segmentation_platform::TipIdentifier)identifier;
+
+// Unique identifier for the given tip.
+@property(nonatomic, readonly) segmentation_platform::TipIdentifier identifier;
+
+// The product image URL associated with the tip. Can be `nil`.
+@property(nonatomic, readwrite) NSURL* productImageURL;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_TIPS_TIPS_MODULE_STATE_H_
diff --git a/ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.mm b/ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.mm
new file mode 100644
index 0000000..dd1e23b3
--- /dev/null
+++ b/ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.mm
@@ -0,0 +1,40 @@
+// 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/ui/content_suggestions/tips/tips_module_state.h"
+
+#import "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h"
+#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h"
+
+using segmentation_platform::TipIdentifier;
+
+@implementation TipsModuleState
+
+- (instancetype)initWithIdentifier:(TipIdentifier)identifier {
+  if ((self = [super init])) {
+    _identifier = identifier;
+  }
+
+  return self;
+}
+
+#pragma mark - MagicStackModule
+
+- (ContentSuggestionsModuleType)type {
+  // Display the Tips module (with a product image) if the product image URL is
+  // valid and reachable.
+  if (_identifier == TipIdentifier::kLensShop) {
+    NSError* err;
+    [self.productImageURL checkResourceIsReachableAndReturnError:&err];
+
+    // Verify the product image URL is reachable.
+    if (!err) {
+      return ContentSuggestionsModuleType::kTipsWithProductImage;
+    }
+  }
+
+  return ContentSuggestionsModuleType::kTips;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/content_suggestions/tips/tips_module_view.h b/ios/chrome/browser/ui/content_suggestions/tips/tips_module_view.h
new file mode 100644
index 0000000..0a85f58
--- /dev/null
+++ b/ios/chrome/browser/ui/content_suggestions/tips/tips_module_view.h
@@ -0,0 +1,20 @@
+// 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 IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_TIPS_TIPS_MODULE_VIEW_H_
+#define IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_TIPS_TIPS_MODULE_VIEW_H_
+
+#import <UIKit/UIKit.h>
+
+@class TipsModuleState;
+
+// A view displaying the Tips module in the Magic Stack.
+@interface TipsModuleView : UIView
+
+// Initializes the `TipsModuleView` with `state`.
+- (instancetype)initWithState:(TipsModuleState*)state;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_TIPS_TIPS_MODULE_VIEW_H_
diff --git a/ios/chrome/browser/ui/content_suggestions/tips/tips_module_view.mm b/ios/chrome/browser/ui/content_suggestions/tips/tips_module_view.mm
new file mode 100644
index 0000000..f2353bc
--- /dev/null
+++ b/ios/chrome/browser/ui/content_suggestions/tips/tips_module_view.mm
@@ -0,0 +1,214 @@
+// 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/ui/content_suggestions/tips/tips_module_view.h"
+
+#import "base/check.h"
+#import "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h"
+#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/icon_detail_view.h"
+#import "ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.h"
+#import "ios/chrome/common/ui/util/constraints_ui_util.h"
+#import "ios/chrome/grit/ios_branded_strings.h"
+#import "ios/chrome/grit/ios_strings.h"
+#import "ui/base/l10n/l10n_util.h"
+
+using segmentation_platform::TipIdentifier;
+
+namespace {
+
+// `TipsModuleView` Accessibility ID.
+NSString* const kTipsModuleViewID = @"kTipsModuleViewID";
+
+// Accessibility ID for an unknown tip.
+NSString* const kUnknownAccessibilityID = @"kUnknownAccessibilityID";
+
+// Accessibility ID for the Lens Search tip.
+NSString* const kLensSearchAccessibilityID = @"kLensSearchAccessibilityID";
+
+// Accessibility ID for the Lens Shop tip.
+NSString* const kLensShopAccessibilityID = @"kLensShopAccessibilityID";
+
+// Accessibility ID for the Lens Translate tip.
+NSString* const kLensTranslateAccessibilityID =
+    @"kLensTranslateAccessibilityID";
+
+// Accessibility ID for the Address Bar Position tip.
+NSString* const kAddressBarPositionAccessibilityID =
+    @"kAddressBarPositionAccessibilityID";
+
+// Accessibility ID for the Save Passwords tip.
+NSString* const kSavePasswordsAccessibilityID =
+    @"kSavePasswordsAccessibilityID";
+
+// Accessibility ID for the Autofill Passwords tip.
+NSString* const kAutofillPasswordsAccessibilityID =
+    @"kAutofillPasswordsAccessibilityID";
+
+// Accessibility ID for the Enhanced Safe Browsing tip.
+NSString* const kEnhancedSafeBrowsingAccessibilityID =
+    @"kEnhancedSafeBrowsingAccessibilityID";
+
+}  // namespace
+
+@implementation TipsModuleView {
+  // The current state of the Tips module.
+  TipsModuleState* _state;
+
+  // The root view of the Tips module.
+  UIView* _contentView;
+}
+
+- (instancetype)initWithState:(TipsModuleState*)state {
+  if ((self = [super init])) {
+    _state = state;
+  }
+
+  return self;
+}
+
+#pragma mark - UIView
+
+- (void)willMoveToSuperview:(UIView*)newSuperview {
+  [super willMoveToSuperview:newSuperview];
+
+  [self createSubviews];
+}
+
+#pragma mark - Private methods
+
+- (void)createSubviews {
+  // Return if the subviews have already been created and added.
+  if (!(self.subviews.count == 0)) {
+    return;
+  }
+
+  self.translatesAutoresizingMaskIntoConstraints = NO;
+  self.accessibilityIdentifier = kTipsModuleViewID;
+
+  TipIdentifier tip = _state.identifier;
+
+  NSString* symbolName = [self symbolNameForTip:tip];
+
+  // `kListBulletClipboardSymbol` and `kGlobeAmericasSymbol` are the only
+  // default symbols used.
+  BOOL isDefaultSymbol =
+      [symbolName isEqualToString:kListBulletClipboardSymbol] ||
+      [symbolName isEqualToString:kGlobeAmericasSymbol];
+
+  _contentView = [[IconDetailView alloc]
+                initWithTitle:[self titleText:tip]
+                  description:[self descriptionTextForTip:tip]
+                   layoutType:IconDetailViewLayoutType::kHero
+                   symbolName:symbolName
+            usesDefaultSymbol:isDefaultSymbol
+                showCheckmark:NO
+      accessibilityIdentifier:[self accessibilityIdentifierForTip:tip]];
+
+  [self addSubview:_contentView];
+
+  AddSameConstraints(_contentView, self);
+
+  return;
+}
+
+// Returns the title text for the given `tip`.
+- (NSString*)titleText:(TipIdentifier)tip {
+  switch (tip) {
+    case TipIdentifier::kUnknown:
+      // An unknown tip does not use a title.
+      return @"";
+    case TipIdentifier::kLensSearch:
+      return l10n_util::GetNSString(IDS_IOS_MAGIC_STACK_TIP_LENS_DEFAULT_TITLE);
+    case TipIdentifier::kLensShop:
+      return l10n_util::GetNSString(IDS_IOS_MAGIC_STACK_TIP_LENS_SHOP_TITLE);
+    case TipIdentifier::kLensTranslate:
+      return l10n_util::GetNSString(
+          IDS_IOS_MAGIC_STACK_TIP_LENS_TRANSLATE_TITLE);
+    case TipIdentifier::kAddressBarPosition:
+      return l10n_util::GetNSString(IDS_IOS_MAGIC_STACK_TIP_ADDRESS_BAR_TITLE);
+    case TipIdentifier::kSavePasswords:
+      return l10n_util::GetNSString(
+          IDS_IOS_MAGIC_STACK_TIP_SAVE_PASSWORD_TITLE);
+    case TipIdentifier::kAutofillPasswords:
+      return l10n_util::GetNSString(
+          IDS_IOS_MAGIC_STACK_TIP_AUTOFILL_PASSWORDS_TITLE);
+    case TipIdentifier::kEnhancedSafeBrowsing:
+      return l10n_util::GetNSString(
+          IDS_IOS_MAGIC_STACK_TIP_SAFE_BROWSING_TITLE);
+  }
+}
+
+// Returns the description text for the given `tip`.
+- (NSString*)descriptionTextForTip:(TipIdentifier)tip {
+  switch (tip) {
+    case TipIdentifier::kUnknown:
+      // An unknown tip does not use a description.
+      return @"";
+    case TipIdentifier::kLensSearch:
+      return l10n_util::GetNSString(
+          IDS_IOS_MAGIC_STACK_TIP_LENS_DEFAULT_DESCRIPTION);
+    case TipIdentifier::kLensShop:
+      return l10n_util::GetNSString(
+          IDS_IOS_MAGIC_STACK_TIP_LENS_SHOP_DESCRIPTION);
+    case TipIdentifier::kLensTranslate:
+      return l10n_util::GetNSString(
+          IDS_IOS_MAGIC_STACK_TIP_LENS_TRANSLATE_DESCRIPTION);
+    case TipIdentifier::kAddressBarPosition:
+      return l10n_util::GetNSString(
+          IDS_IOS_MAGIC_STACK_TIP_ADDRESS_BAR_DESCRIPTION);
+    case TipIdentifier::kSavePasswords:
+      return l10n_util::GetNSString(
+          IDS_IOS_MAGIC_STACK_TIP_SAVE_PASSWORD_DESCRIPTION);
+    case TipIdentifier::kAutofillPasswords:
+      return l10n_util::GetNSString(
+          IDS_IOS_MAGIC_STACK_TIP_AUTOFILL_PASSWORDS_DESCRIPTION);
+    case TipIdentifier::kEnhancedSafeBrowsing:
+      return l10n_util::GetNSString(
+          IDS_IOS_MAGIC_STACK_TIP_SAFE_BROWSING_DESCRIPTION);
+  }
+}
+
+// Returns the symbol name for the given `tip`.
+- (NSString*)symbolNameForTip:(TipIdentifier)tip {
+  switch (tip) {
+    case TipIdentifier::kUnknown:
+      return kListBulletClipboardSymbol;
+    case TipIdentifier::kLensSearch:
+    case TipIdentifier::kLensShop:
+    case TipIdentifier::kLensTranslate:
+      return kCameraLensSymbol;
+    case TipIdentifier::kAddressBarPosition:
+      return kGlobeAmericasSymbol;
+    case TipIdentifier::kSavePasswords:
+    case TipIdentifier::kAutofillPasswords:
+      return kPasswordSymbol;
+    case TipIdentifier::kEnhancedSafeBrowsing:
+      return kPrivacySymbol;
+  }
+}
+
+// Returns the accessibility identifier for the specified `tip`.
+- (NSString*)accessibilityIdentifierForTip:(TipIdentifier)tip {
+  switch (tip) {
+    case TipIdentifier::kUnknown:
+      return kUnknownAccessibilityID;
+    case TipIdentifier::kLensSearch:
+      return kLensSearchAccessibilityID;
+    case TipIdentifier::kLensShop:
+      return kLensShopAccessibilityID;
+    case TipIdentifier::kLensTranslate:
+      return kLensTranslateAccessibilityID;
+    case TipIdentifier::kAddressBarPosition:
+      return kAddressBarPositionAccessibilityID;
+    case TipIdentifier::kSavePasswords:
+      return kSavePasswordsAccessibilityID;
+    case TipIdentifier::kAutofillPasswords:
+      return kAutofillPasswordsAccessibilityID;
+    case TipIdentifier::kEnhancedSafeBrowsing:
+      return kEnhancedSafeBrowsingAccessibilityID;
+  }
+}
+
+@end
diff --git a/ios/chrome/browser/ui/content_suggestions/tips/tips_module_view_unittest.mm b/ios/chrome/browser/ui/content_suggestions/tips/tips_module_view_unittest.mm
new file mode 100644
index 0000000..3236b3b
--- /dev/null
+++ b/ios/chrome/browser/ui/content_suggestions/tips/tips_module_view_unittest.mm
@@ -0,0 +1,118 @@
+// 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//ui/content_suggestions/tips/tips_module_view.h"
+
+#import <Foundation/Foundation.h>
+
+#import "base/test/task_environment.h"
+#import "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/icon_detail_view.h"
+#import "ios/chrome/browser/ui/content_suggestions/tips/tips_module_state.h"
+#import "testing/platform_test.h"
+
+using segmentation_platform::TipIdentifier;
+
+// Tests the `TipsModuleView` and subviews.
+class TipsModuleViewTest : public PlatformTest {
+ public:
+  TipsModuleViewTest() {
+    _superview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
+
+    _window = [[UIWindow alloc] init];
+
+    [_window addSubview:_superview];
+
+    UIView.animationsEnabled = NO;
+  }
+
+  // Iterates a view's subviews recursively, calling the block with each one.
+  void IterateSubviews(UIView* view, bool (^block)(UIView* subview)) {
+    for (UIView* subview in view.subviews) {
+      bool should_break = block(subview);
+
+      if (should_break) {
+        break;
+      }
+
+      IterateSubviews(subview, block);
+    }
+  }
+
+  // Searches recursively through subviews to find one with the given
+  // `accessibility_id`.
+  UIView* FindSubview(NSString* accessibility_id) {
+    __block UIView* found = nil;
+
+    IterateSubviews(_superview, ^bool(UIView* subview) {
+      if (subview.accessibilityIdentifier == accessibility_id) {
+        found = subview;
+
+        return true;
+      }
+
+      return false;
+    });
+
+    return found;
+  }
+
+  // Expects a subview with the given `accessibility_id` to either exist or
+  // or not.
+  void ExpectSubview(NSString* accessibility_id, bool exists) {
+    UIView* subview = FindSubview(accessibility_id);
+
+    if (exists) {
+      EXPECT_NE(subview, nil);
+    } else {
+      EXPECT_EQ(subview, nil);
+    }
+  }
+
+  // Returns a count of subviews of the given `klass`.
+  int CountSubviewsWithClass(UIView* view, Class klass) {
+    __block int count = 0;
+
+    IterateSubviews(view, ^bool(UIView* subview) {
+      if ([subview class] == klass) {
+        count++;
+      }
+
+      return false;
+    });
+
+    return count;
+  }
+
+  // Expects `count` subviews of the given `klass` to exist.
+  void ExpectSubviewCount(int count, Class klass) {
+    int actual_count = CountSubviewsWithClass(_superview, klass);
+
+    EXPECT_EQ(actual_count, count);
+  }
+
+ protected:
+  base::test::SingleThreadTaskEnvironment _task_environment;
+  UIWindow* _window;
+  UIView* _superview;
+};
+
+// Tests that the module can be initialized, create subviews, and that the
+// correct module state is displayed.
+TEST_F(TipsModuleViewTest, DisplaysModuleWithDefaultState) {
+  TipsModuleState* state = [[TipsModuleState alloc]
+      initWithIdentifier:TipIdentifier::kLensTranslate];
+
+  TipsModuleView* view = [[TipsModuleView alloc] initWithState:state];
+
+  [_superview addSubview:view];
+
+  // It should initially display one item, i.e. the hero-cell default layout
+  // item.
+  ExpectSubviewCount(1, [IconDetailView class]);
+
+  ExpectSubview(@"kTipsModuleViewID", true);
+  ExpectSubview(@"kLensTranslateAccessibilityID", true);
+  ExpectSubview(@"kLensShopAccessibilityID", false);
+}
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index 100cf85d..2f32c68 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -413,6 +413,7 @@
     "//ios/chrome/browser/ui/content_suggestions/safety_check:unit_tests",
     "//ios/chrome/browser/ui/content_suggestions/set_up_list:unit_tests",
     "//ios/chrome/browser/ui/content_suggestions/tab_resumption:unit_tests",
+    "//ios/chrome/browser/ui/content_suggestions/tips:unit_tests",
     "//ios/chrome/browser/ui/fullscreen:unit_tests",
     "//ios/chrome/browser/ui/infobars/banners:unit_tests",
     "//ios/chrome/browser/ui/infobars/modals/autofill_address_profile:unit_tests",
diff --git a/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java b/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java
index f4ed366d..b8d3a19bc 100644
--- a/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java
+++ b/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java
@@ -549,6 +549,11 @@
     }
 
     @CalledByNative
+    private boolean isSoftwareCodec() {
+        return MediaCodecUtil.isSoftwareCodec(mMediaCodec.getCodecInfo());
+    }
+
+    @CalledByNative
     private MediaFormatWrapper getOutputFormat() {
         if (mUseAsyncApi && mCurrentFormat != null) return mCurrentFormat;
 
diff --git a/media/base/android/media_codec_bridge.h b/media/base/android/media_codec_bridge.h
index 5711ae8..62cd6a4e 100644
--- a/media/base/android/media_codec_bridge.h
+++ b/media/base/android/media_codec_bridge.h
@@ -203,6 +203,9 @@
   // Gets the component name. Before API level 18 this returns an empty string.
   virtual std::string GetName() = 0;
 
+  // Returns whether the media codec implementation is software codec.
+  virtual bool IsSoftwareCodec() = 0;
+
   // Changes the output surface for the MediaCodec. May only be used on API
   // level 23 and higher (Marshmallow).
   virtual bool SetSurface(const base::android::JavaRef<jobject>& surface) = 0;
diff --git a/media/base/android/media_codec_bridge_impl.cc b/media/base/android/media_codec_bridge_impl.cc
index eadf832..ea08e6ef 100644
--- a/media/base/android/media_codec_bridge_impl.cc
+++ b/media/base/android/media_codec_bridge_impl.cc
@@ -850,6 +850,11 @@
   return ConvertJavaStringToUTF8(env, j_name);
 }
 
+bool MediaCodecBridgeImpl::IsSoftwareCodec() {
+  JNIEnv* env = AttachCurrentThread();
+  return Java_MediaCodecBridge_isSoftwareCodec(env, j_bridge_);
+}
+
 bool MediaCodecBridgeImpl::SetSurface(const JavaRef<jobject>& surface) {
   JNIEnv* env = AttachCurrentThread();
   return Java_MediaCodecBridge_setSurface(env, j_bridge_, surface);
diff --git a/media/base/android/media_codec_bridge_impl.h b/media/base/android/media_codec_bridge_impl.h
index f808e52..94f94f25 100644
--- a/media/base/android/media_codec_bridge_impl.h
+++ b/media/base/android/media_codec_bridge_impl.h
@@ -164,6 +164,7 @@
                                         void* dst,
                                         size_t num) override;
   std::string GetName() override;
+  bool IsSoftwareCodec() override;
   bool SetSurface(const base::android::JavaRef<jobject>& surface) override;
   void SetVideoBitrate(int bps, int frame_rate) override;
   void RequestKeyFrameSoon() override;
diff --git a/media/base/android/mock_media_codec_bridge.cc b/media/base/android/mock_media_codec_bridge.cc
index de67d5195..11dff42 100644
--- a/media/base/android/mock_media_codec_bridge.cc
+++ b/media/base/android/mock_media_codec_bridge.cc
@@ -49,6 +49,10 @@
   return name_;
 }
 
+bool MockMediaCodecBridge::IsSoftwareCodec() {
+  return is_software_codec_;
+}
+
 CodecType MockMediaCodecBridge::GetCodecType() const {
   return codec_type_;
 }
@@ -68,7 +72,7 @@
       config.codec_type == CodecType::kSoftware ? "android" : "google",
       GetCodecName(config.codec).c_str(),
       config.codec_type == CodecType::kSecure ? ".secure" : "");
-
+  bridge->is_software_codec_ = config.codec_type == CodecType::kSoftware;
   return bridge;
 }
 
diff --git a/media/base/android/mock_media_codec_bridge.h b/media/base/android/mock_media_codec_bridge.h
index 41f2b465..62bf9ca8 100644
--- a/media/base/android/mock_media_codec_bridge.h
+++ b/media/base/android/mock_media_codec_bridge.h
@@ -81,6 +81,7 @@
       CopyFromOutputBuffer,
       MediaCodecResult(int index, size_t offset, void* dst, size_t num));
   std::string GetName() override;
+  bool IsSoftwareCodec() override;
   MOCK_METHOD1(SetSurface,
                bool(const base::android::JavaRef<jobject>& surface));
   MOCK_METHOD2(SetVideoBitrate, void(int bps, int frame_rate));
@@ -105,6 +106,7 @@
 
   CodecType codec_type_ = CodecType::kAny;
   std::string name_;
+  bool is_software_codec_;
 };
 
 }  // namespace media
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 0e6f191..bdde687d 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -1784,6 +1784,12 @@
              "MediaSharedBitmapToSharedImage",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+#if BUILDFLAG(IS_WIN)
+BASE_FEATURE(kMediaFoundationD3DVideoProcessing,
+             "MediaFoundationD3DVideoProcessing",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+#endif
+
 bool IsChromeWideEchoCancellationEnabled() {
 #if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
   return base::FeatureList::IsEnabled(kChromeWideEchoCancellation);
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 73adcc22..b1f6532 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -546,6 +546,10 @@
 
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kMediaSharedBitmapToSharedImage);
 
+#if BUILDFLAG(IS_WIN)
+MEDIA_EXPORT BASE_DECLARE_FEATURE(kMediaFoundationD3DVideoProcessing);
+#endif
+
 // Based on a |command_line| and the current platform, returns the effective
 // autoplay policy. In other words, it will take into account the default policy
 // if none is specified via the command line and options passed for testing.
diff --git a/media/base/win/mf_helpers.cc b/media/base/win/mf_helpers.cc
index 45f4d127..1934685b 100644
--- a/media/base/win/mf_helpers.cc
+++ b/media/base/win/mf_helpers.cc
@@ -596,21 +596,21 @@
 }
 
 MFVideoPrimaries VideoPrimariesToMFVideoPrimaries(
-    VideoColorSpace::PrimaryID primaries) {
+    gfx::ColorSpace::PrimaryID primaries) {
   switch (primaries) {
-    case VideoColorSpace::PrimaryID::BT709:
+    case gfx::ColorSpace::PrimaryID::BT709:
       return MFVideoPrimaries_BT709;
-    case VideoColorSpace::PrimaryID::BT470M:
+    case gfx::ColorSpace::PrimaryID::BT470M:
       return MFVideoPrimaries_BT470_2_SysM;
-    case VideoColorSpace::PrimaryID::BT470BG:
+    case gfx::ColorSpace::PrimaryID::BT470BG:
       return MFVideoPrimaries_BT470_2_SysBG;
-    case VideoColorSpace::PrimaryID::SMPTE170M:
+    case gfx::ColorSpace::PrimaryID::SMPTE170M:
       return MFVideoPrimaries_SMPTE170M;
-    case VideoColorSpace::PrimaryID::SMPTE240M:
+    case gfx::ColorSpace::PrimaryID::SMPTE240M:
       return MFVideoPrimaries_SMPTE240M;
-    case VideoColorSpace::PrimaryID::BT2020:
+    case gfx::ColorSpace::PrimaryID::BT2020:
       return MFVideoPrimaries_BT2020;
-    case VideoColorSpace::PrimaryID::EBU_3213_E:
+    case gfx::ColorSpace::PrimaryID::EBU_3213_E:
       return MFVideoPrimaries_EBU3213;
     default:
       return MFVideoPrimaries_Unknown;
@@ -896,6 +896,13 @@
                              kOneMicrosecondInMFSampleTimeUnits);
   RETURN_ON_HR_FAILURE(hr, "Failed to set sample timestamp", hr);
 
+  if (frame->ColorSpace().GetPrimaryID() !=
+      gfx::ColorSpace::PrimaryID::INVALID) {
+    hr = sample->SetUINT32(
+        MF_MT_VIDEO_PRIMARIES,
+        VideoPrimariesToMFVideoPrimaries(frame->ColorSpace().GetPrimaryID()));
+  }
+
   *sample_out = sample.Detach();
 
   return S_OK;
diff --git a/media/base/win/mf_helpers.h b/media/base/win/mf_helpers.h
index a3dad8d..37361429 100644
--- a/media/base/win/mf_helpers.h
+++ b/media/base/win/mf_helpers.h
@@ -185,7 +185,7 @@
 
 // Converts `primaries` into an MFVideoPrimaries value
 MEDIA_EXPORT MFVideoPrimaries
-VideoPrimariesToMFVideoPrimaries(VideoColorSpace::PrimaryID primaries);
+VideoPrimariesToMFVideoPrimaries(gfx::ColorSpace::PrimaryID primaries);
 
 // Callback to transform a Media Foundation sample when converting from the
 // DecoderBuffer if needed.
diff --git a/media/capture/video/mac/uvc_control_mac.mm b/media/capture/video/mac/uvc_control_mac.mm
index 1e8d0af..cf79744 100644
--- a/media/capture/video/mac/uvc_control_mac.mm
+++ b/media/capture/video/mac/uvc_control_mac.mm
@@ -273,10 +273,10 @@
 std::vector<uint8_t> ExtractControls(IOUSBDescriptorHeader* usb_descriptor) {
   auto* descriptor = reinterpret_cast<DescriptorType>(usb_descriptor);
   if (descriptor->bControlSize > 0) {
-    NSData* data = [[NSData alloc] initWithBytes:&descriptor->bmControls[0]
-                                          length:descriptor->bControlSize];
-    const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data.bytes);
-    return std::vector<uint8_t>(bytes, bytes + data.length);
+    const uint8_t* bytes =
+        reinterpret_cast<const uint8_t*>(&descriptor->bmControls[0]);
+    const size_t length = descriptor->bControlSize;
+    return std::vector<uint8_t>(bytes, bytes + length);
   }
   return std::vector<uint8_t>();
 }
diff --git a/media/gpu/android/media_codec_video_decoder.cc b/media/gpu/android/media_codec_video_decoder.cc
index 00904a8..8e5f3e4 100644
--- a/media/gpu/android/media_codec_video_decoder.cc
+++ b/media/gpu/android/media_codec_video_decoder.cc
@@ -122,9 +122,7 @@
 // Return the name of the decoder that will be used to create MediaCodec.
 void SelectMediaCodec(const VideoDecoderConfig& config,
                       bool requires_secure_codec,
-                      std::string* out_codec_name,
-                      bool* out_is_software_codec) {
-  *out_is_software_codec = false;
+                      std::string* out_codec_name) {
   *out_codec_name = "";
 
   std::string software_decoder;
@@ -170,7 +168,6 @@
       continue;
     }
 
-    *out_is_software_codec = false;
     *out_codec_name = info.name;
     return;
   }
@@ -195,7 +192,6 @@
     return;
   }
 
-  *out_is_software_codec = true;
   *out_codec_name = software_decoder;
 }
 
@@ -705,8 +701,7 @@
   config->hdr_metadata = decoder_config_.hdr_metadata();
   config->use_block_model = use_block_model_;
   config->profile = decoder_config_.profile();
-  SelectMediaCodec(decoder_config_, requires_secure_codec_, &config->name,
-                   &is_software_codec_);
+  SelectMediaCodec(decoder_config_, requires_secure_codec_, &config->name);
 
   config->on_buffers_available_cb =
       base::BindPostTaskToCurrentDefault(base::BindRepeating(
@@ -770,8 +765,9 @@
   }
 
   codec_name_ = codec->GetName();
-  MEDIA_LOG(INFO, media_log_) << "Created MediaCodec " << codec_name_
-                              << ", is_software_codec=" << is_software_codec_;
+  MEDIA_LOG(INFO, media_log_)
+      << "Created MediaCodec " << codec_name_
+      << ", is_software_codec=" << codec->IsSoftwareCodec();
 
   // Since we can't get the coded size w/o rendering the frame, we try to guess
   // in cases where we are unable to render the frame (resolution changes). If
@@ -965,10 +961,8 @@
     // The underlying MediaCodec must remain the same in order for us to elide
     // the end of stream flush.
     const bool can_reuse_codec = [&]() {
-      bool unused_is_sw_codec;
       std::string codec_name;
-      SelectMediaCodec(new_config, requires_secure_codec_, &codec_name,
-                       &unused_is_sw_codec);
+      SelectMediaCodec(new_config, requires_secure_codec_, &codec_name);
       return !codec_name_.empty() && codec_name == codec_name_ &&
              !CodecNeedsReallocation(new_config.coded_size().width());
     }();
diff --git a/media/gpu/android/media_codec_video_decoder.h b/media/gpu/android/media_codec_video_decoder.h
index b4822282..07bd24ae 100644
--- a/media/gpu/android/media_codec_video_decoder.h
+++ b/media/gpu/android/media_codec_video_decoder.h
@@ -355,9 +355,6 @@
   // it fails to get a codec.  This is to work around b/191966399.
   bool should_retry_codec_allocation_ = false;
 
-  // True if the created codec is software backed.
-  bool is_software_codec_ = false;
-
   // Name of the MediaCodec that was created.
   std::string codec_name_;
 
diff --git a/media/gpu/mac/vt_config_util_unittest.mm b/media/gpu/mac/vt_config_util_unittest.mm
index cc53606..dcf638f 100644
--- a/media/gpu/mac/vt_config_util_unittest.mm
+++ b/media/gpu/mac/vt_config_util_unittest.mm
@@ -53,10 +53,7 @@
 base::span<const uint8_t> GetDataValue(CFDictionaryRef dict, CFStringRef key) {
   CFDataRef data =
       base::apple::CFCastStrict<CFDataRef>(CFDictionaryGetValue(dict, key));
-  return data ? base::span<const uint8_t>(
-                    reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(data)),
-                    base::checked_cast<size_t>(CFDataGetLength(data)))
-              : base::span<const uint8_t>();
+  return data ? base::apple::CFDataToSpan(data) : base::span<const uint8_t>();
 }
 
 base::span<const uint8_t> GetNestedDataValue(CFDictionaryRef dict,
diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
index c346fec..a68cda8 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
@@ -23,6 +23,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/containers/fixed_flat_set.h"
 #include "base/features.h"
 #include "base/memory/shared_memory_mapping.h"
 #include "base/memory/unsafe_shared_memory_region.h"
@@ -103,6 +104,20 @@
 constexpr uint8_t kH264MinQuantizer = 16;
 constexpr uint8_t kH264MaxQuantizer = 51;
 
+// NV12 is supported natively by all hardware encoders.  Other
+// formats can be converted by MediaFoundationVideoProcessorAccelerator.
+// In the future, specific encoders may also be queried for support
+// for additional formats.  For example, ARGB may be accepted by
+// some encoders directly, or AV1 encoders may accept some 4:4:4
+// formats.
+constexpr auto kSupportedPixelFormats =
+    base::MakeFixedFlatSet<VideoPixelFormat>(
+        {PIXEL_FORMAT_I420, PIXEL_FORMAT_NV12});
+constexpr auto kSupportedPixelFormatsD3DVideoProcessing =
+    base::MakeFixedFlatSet<VideoPixelFormat>(
+        {PIXEL_FORMAT_I420, PIXEL_FORMAT_NV12, PIXEL_FORMAT_YV12,
+         PIXEL_FORMAT_NV21, PIXEL_FORMAT_ARGB, PIXEL_FORMAT_XRGB});
+
 #if BUILDFLAG(ENABLE_PLATFORM_HEVC)
 // For H.265, ideally we may reuse Min/MaxQp for H.264 from
 // media/gpu/vaapi/h264_vaapi_video_encoder_delegate.cc. However
@@ -783,6 +798,7 @@
     CHROME_LUID luid)
     : task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
       luid_(luid),
+      gpu_preferences_(gpu_preferences),
       workarounds_(gpu_workarounds) {
   weak_ptr_ = weak_factory_.GetWeakPtr();
   bitrate_allocation_.SetBitrate(0, 0, kDefaultTargetBitrate);
@@ -866,6 +882,11 @@
       }
     }
 
+    if (base::FeatureList::IsEnabled(kMediaFoundationD3DVideoProcessing)) {
+      base::ranges::copy(kSupportedPixelFormatsD3DVideoProcessing,
+                         profile.gpu_supported_pixel_formats.begin());
+    }
+
     SupportedProfile portrait_profile(profile);
     portrait_profile.max_resolution.Transpose();
     portrait_profile.min_resolution.Transpose();
@@ -900,8 +921,18 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   media_log_ = std::move(media_log);
 
-  if (PIXEL_FORMAT_I420 != config.input_format &&
-      PIXEL_FORMAT_NV12 != config.input_format) {
+  bool is_supported_format = false;
+  if (base::FeatureList::IsEnabled(kMediaFoundationD3DVideoProcessing)) {
+    is_supported_format =
+        base::ranges::find(kSupportedPixelFormatsD3DVideoProcessing,
+                           config.input_format) != kSupportedPixelFormats.end();
+  } else {
+    is_supported_format =
+        base::ranges::find(kSupportedPixelFormats, config.input_format) !=
+        kSupportedPixelFormats.end();
+  }
+
+  if (!is_supported_format) {
     MEDIA_LOG(ERROR, media_log_)
         << "Input format not supported= "
         << VideoPixelFormatToString(config.input_format);
@@ -1120,6 +1151,44 @@
     encoder_info_sent_ = true;
   }
 
+  if (!base::FeatureList::IsEnabled(kMediaFoundationD3DVideoProcessing) ||
+      config.input_format == PIXEL_FORMAT_NV12) {
+    return true;
+  }
+
+  mf_video_processor_ =
+      std::make_unique<MediaFoundationVideoProcessorAccelerator>(
+          gpu_preferences_, workarounds_);
+  MediaFoundationVideoProcessorAccelerator::Config vp_config;
+  vp_config.input_format = config.input_format;
+  vp_config.input_visible_size = config.input_visible_size;
+  // Primaries information is provided per frame and will be
+  // attached to the corresponding IMFSample.  This color
+  // space information now serves as a default if frame
+  // primaries are unknown.
+  vp_config.input_color_space = gfx::ColorSpace::CreateREC709();
+  vp_config.output_format = VideoPixelFormat::PIXEL_FORMAT_NV12;
+  vp_config.output_visible_size = config.input_visible_size;
+  vp_config.output_color_space = gfx::ColorSpace::CreateREC709();
+  if (dxgi_resource_mapping_required_) {
+    hr = mf_video_processor_->Initialize(vp_config, nullptr,
+                                         media_log_->Clone());
+  } else {
+    hr = mf_video_processor_->Initialize(vp_config, dxgi_device_manager_,
+                                         media_log_->Clone());
+  }
+
+  if (FAILED(hr)) {
+    NotifyErrorStatus({EncoderStatus::Codes::kEncoderInitializationError,
+                       "Couldn't initialize MF video processor for color "
+                       "format conversion"});
+    return false;
+  }
+
+  MEDIA_LOG(INFO, media_log_)
+      << "Using video processor to convert from " << config.input_format
+      << " to encoder accepted " << vp_config.output_format;
+
   return true;
 }
 
@@ -2332,6 +2401,14 @@
   RETURN_ON_HR_FAILURE(hr, "Failed to remove buffers from sample", hr);
   hr = input_sample_->AddBuffer(input_buffer.Get());
   RETURN_ON_HR_FAILURE(hr, "Failed to add buffer to sample", hr);
+
+  if (mf_video_processor_) {
+    // This sample needs color space conversion
+    ComMFSample vp_input_sample = std::move(input_sample_);
+    hr = mf_video_processor_->Convert(vp_input_sample.Get(), &input_sample_);
+    RETURN_ON_HR_FAILURE(hr, "Failed to convert input frame", hr);
+  }
+
   return S_OK;
 }
 
@@ -2343,6 +2420,20 @@
             VideoFrame::StorageType::STORAGE_GPU_MEMORY_BUFFER);
   DCHECK(dxgi_device_manager_);
 
+  if (mf_video_processor_) {
+    // Using the MF video processor mitigates many of the issues handled below.
+    // - MFVP will resize if needed
+    // - MFVP acquires the texture's keyed mutex when available and
+    //    holds it only for the duration needed.
+    // - MFVP will call SetCurrentLength on the output buffer
+    // - MFVP will output a different texture that can be used
+    //    as encoder input with no synchronization issues.
+    input_sample_ = nullptr;
+    HRESULT hr = mf_video_processor_->Convert(frame, &input_sample_);
+    RETURN_ON_HR_FAILURE(hr, "Failed to convert input frame", hr);
+    return S_OK;
+  }
+
   gfx::GpuMemoryBufferHandle buffer_handle = frame->GetGpuMemoryBufferHandle();
   CHECK(!buffer_handle.is_null());
   CHECK_EQ(buffer_handle.type, gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE);
diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.h b/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
index eab1b4d..4105da6 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
@@ -32,6 +32,7 @@
 #include "media/base/win/dxgi_device_manager.h"
 #include "media/gpu/media_gpu_export.h"
 #include "media/gpu/windows/d3d_com_defs.h"
+#include "media/gpu/windows/mf_video_processor_accelerator.h"
 #include "media/video/video_encode_accelerator.h"
 
 namespace media {
@@ -291,6 +292,10 @@
   // of the next frame to be encoded.
   bool has_prepared_input_sample_ = false;
 
+  // MF video processor used for color format conversion; only
+  // created if needed.
+  std::unique_ptr<MediaFoundationVideoProcessorAccelerator> mf_video_processor_;
+
   // Variables used by video processing for scaling.
   ComD3D11VideoProcessor video_processor_;
   ComD3D11VideoProcessorEnumerator video_processor_enumerator_;
@@ -330,6 +335,7 @@
   // output. Every input pushes back a new entry, and outputs consumes entries
   // from the front.
   base::circular_deque<OutOfBandMetadata> sample_metadata_queue_;
+  gpu::GpuPreferences gpu_preferences_;
   gpu::GpuDriverBugWorkarounds workarounds_;
 
   // This counter starts from 0, used for managing the METransformNeedInput
diff --git a/media/gpu/windows/mf_video_processor_accelerator.cc b/media/gpu/windows/mf_video_processor_accelerator.cc
index 1e30ba0..0158a03f 100644
--- a/media/gpu/windows/mf_video_processor_accelerator.cc
+++ b/media/gpu/windows/mf_video_processor_accelerator.cc
@@ -91,8 +91,8 @@
                            1);
   RETURN_ON_HR_FAILURE(hr, "Couldn't set pixel aspect ratio", false);
   hr = input_media_type_->SetUINT32(
-      MF_MT_VIDEO_PRIMARIES,
-      VideoPrimariesToMFVideoPrimaries(config.input_color_space.primaries));
+      MF_MT_VIDEO_PRIMARIES, VideoPrimariesToMFVideoPrimaries(
+                                 config.input_color_space.GetPrimaryID()));
   RETURN_ON_HR_FAILURE(hr, "Couldn't set video primaries", false);
   if (config.input_format == PIXEL_FORMAT_XRGB ||
       config.input_format == PIXEL_FORMAT_ARGB) {
@@ -135,8 +135,8 @@
                            1);
   RETURN_ON_HR_FAILURE(hr, L"Couldn't set pixel aspect ratio", false);
   hr = output_media_type->SetUINT32(
-      MF_MT_VIDEO_PRIMARIES,
-      VideoPrimariesToMFVideoPrimaries(config.output_color_space.primaries));
+      MF_MT_VIDEO_PRIMARIES, VideoPrimariesToMFVideoPrimaries(
+                                 config.output_color_space.GetPrimaryID()));
   RETURN_ON_HR_FAILURE(hr, L"Couldn't set video primaries", false);
   hr = output_media_type->SetUINT32(MF_MT_INTERLACE_MODE,
                                     MFVideoInterlace_Progressive);
diff --git a/media/gpu/windows/mf_video_processor_accelerator.h b/media/gpu/windows/mf_video_processor_accelerator.h
index ad94532..e40a020 100644
--- a/media/gpu/windows/mf_video_processor_accelerator.h
+++ b/media/gpu/windows/mf_video_processor_accelerator.h
@@ -32,11 +32,11 @@
   struct MEDIA_GPU_EXPORT Config {
     VideoPixelFormat input_format;
     gfx::Size input_visible_size;
-    VideoColorSpace input_color_space;
+    gfx::ColorSpace input_color_space;
 
     VideoPixelFormat output_format;
     gfx::Size output_visible_size;
-    VideoColorSpace output_color_space;
+    gfx::ColorSpace output_color_space;
   };
 
   explicit MediaFoundationVideoProcessorAccelerator(
diff --git a/media/gpu/windows/mf_video_processor_accelerator_unittest.cc b/media/gpu/windows/mf_video_processor_accelerator_unittest.cc
index 444d24bf..81d9d26 100644
--- a/media/gpu/windows/mf_video_processor_accelerator_unittest.cc
+++ b/media/gpu/windows/mf_video_processor_accelerator_unittest.cc
@@ -179,10 +179,10 @@
   MediaFoundationVideoProcessorAccelerator::Config config;
   config.input_format = VideoPixelFormat::PIXEL_FORMAT_XRGB;
   config.input_visible_size = {kWidth, kHeight};
-  config.input_color_space = VideoColorSpace::REC709();
+  config.input_color_space = gfx::ColorSpace::CreateREC709();
   config.output_format = VideoPixelFormat::PIXEL_FORMAT_NV12;
   config.output_visible_size = {kWidth, kHeight};
-  config.output_color_space = VideoColorSpace::REC709();
+  config.output_color_space = gfx::ColorSpace::CreateREC709();
   ASSERT_TRUE(video_processor->Initialize(config, dxgi_device_man_,
                                           std::make_unique<NullMediaLog>()));
 
@@ -233,10 +233,10 @@
   MediaFoundationVideoProcessorAccelerator::Config config;
   config.input_format = VideoPixelFormat::PIXEL_FORMAT_XRGB;
   config.input_visible_size = {kWidth, kHeight};
-  config.input_color_space = VideoColorSpace::REC709();
+  config.input_color_space = gfx::ColorSpace::CreateREC709();
   config.output_format = VideoPixelFormat::PIXEL_FORMAT_XRGB;
   config.output_visible_size = {kWidth / 2, kHeight / 2};
-  config.output_color_space = VideoColorSpace::REC709();
+  config.output_color_space = gfx::ColorSpace::CreateREC709();
   ASSERT_TRUE(video_processor->Initialize(config, dxgi_device_man_,
                                           std::make_unique<NullMediaLog>()));
 
@@ -292,10 +292,11 @@
   MediaFoundationVideoProcessorAccelerator::Config config;
   config.input_format = VideoPixelFormat::PIXEL_FORMAT_XRGB;
   config.input_visible_size = {kWidth, kHeight};
-  config.input_color_space = VideoColorSpace::REC709();
+  config.input_color_space = gfx::ColorSpace::CreateREC709();
   config.output_format = VideoPixelFormat::PIXEL_FORMAT_NV12;
   config.output_visible_size = {kWidth / 2, kHeight / 2};
-  config.output_color_space = VideoColorSpace::REC709();
+  config.output_color_space = gfx::ColorSpace::CreateREC709();
+  ;
   ASSERT_TRUE(video_processor->Initialize(config, dxgi_device_man_,
                                           std::make_unique<NullMediaLog>()));
 
@@ -364,10 +365,10 @@
   MediaFoundationVideoProcessorAccelerator::Config config;
   config.input_format = VideoPixelFormat::PIXEL_FORMAT_XRGB;
   config.input_visible_size = {kWidth, kHeight};
-  config.input_color_space = VideoColorSpace::REC709();
+  config.input_color_space = gfx::ColorSpace::CreateREC709();
   config.output_format = VideoPixelFormat::PIXEL_FORMAT_NV12;
   config.output_visible_size = {kWidth, kHeight};
-  config.output_color_space = VideoColorSpace::REC709();
+  config.output_color_space = gfx::ColorSpace::CreateREC709();
   ASSERT_TRUE(video_processor->Initialize(config, dxgi_device_man_,
                                           std::make_unique<NullMediaLog>()));
 
@@ -417,10 +418,10 @@
   MediaFoundationVideoProcessorAccelerator::Config config;
   config.input_format = VideoPixelFormat::PIXEL_FORMAT_XRGB;
   config.input_visible_size = {kWidth, kHeight};
-  config.input_color_space = VideoColorSpace::REC709();
+  config.input_color_space = gfx::ColorSpace::CreateREC709();
   config.output_format = VideoPixelFormat::PIXEL_FORMAT_NV12;
   config.output_visible_size = {kWidth, kHeight};
-  config.output_color_space = VideoColorSpace::REC709();
+  config.output_color_space = gfx::ColorSpace::CreateREC709();
   ASSERT_TRUE(video_processor->Initialize(config, nullptr,
                                           std::make_unique<NullMediaLog>()));
 
diff --git a/media/renderers/video_resource_updater.cc b/media/renderers/video_resource_updater.cc
index afbda1b1..2a36dd40 100644
--- a/media/renderers/video_resource_updater.cc
+++ b/media/renderers/video_resource_updater.cc
@@ -23,6 +23,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/shared_memory_mapping.h"
 #include "base/memory/unsafe_shared_memory_region.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/single_thread_task_runner.h"
@@ -775,6 +776,9 @@
 
 void VideoResourceUpdater::ObtainFrameResource(
     scoped_refptr<VideoFrame> video_frame) {
+  UMA_HISTOGRAM_ENUMERATION("Media.VideoResourceUpdater.FrameFormat",
+                            video_frame->format(), PIXEL_FORMAT_MAX + 1);
+
   if (video_frame->metadata().overlay_plane_id.has_value()) {
     // This is a hole punching VideoFrame, there is nothing to display.
     overlay_plane_id_ = *video_frame->metadata().overlay_plane_id;
diff --git a/media/video/video_encode_accelerator_adapter.cc b/media/video/video_encode_accelerator_adapter.cc
index dbd717a0..95e6f39 100644
--- a/media/video/video_encode_accelerator_adapter.cc
+++ b/media/video/video_encode_accelerator_adapter.cc
@@ -95,7 +95,8 @@
     VideoPixelFormat format,
     VideoFrame::StorageType storage_type,
     VideoEncodeAccelerator::SupportedRateControlMode supported_rc_modes,
-    VideoEncodeAccelerator::Config::EncoderType required_encoder_type) {
+    VideoEncodeAccelerator::Config::EncoderType required_encoder_type,
+    bool is_gpu_supported_format) {
   Bitrate bitrate =
       CreateBitrate(opts.bitrate, opts.frame_size, supported_rc_modes);
   auto config = VideoEncodeAccelerator::Config(
@@ -161,8 +162,9 @@
 
   // Override the provided format if incoming frames are RGB -- they'll be
   // converted to I420 or NV12 depending on the VEA configuration.
-  if (is_rgb)
+  if (is_rgb && !is_gpu_supported_format) {
     config.input_format = PIXEL_FORMAT_I420;
+  }
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   if (format != PIXEL_FORMAT_I420 ||
@@ -482,9 +484,12 @@
 
   auto supported_rc_modes =
       VideoEncodeAccelerator::SupportedRateControlMode::kNoMode;
+  std::vector<VideoPixelFormat> gpu_supported_pixel_formats;
   for (const auto& supported_profile : *supported_profiles) {
     if (supported_profile.profile == profile) {
       supported_rc_modes = supported_profile.rate_control_modes;
+      gpu_supported_pixel_formats =
+          supported_profile.gpu_supported_pixel_formats;
       break;
     }
   }
@@ -501,6 +506,7 @@
 
   profile_ = profile;
   supported_rc_modes_ = supported_rc_modes;
+  gpu_supported_pixel_formats_ = std::move(gpu_supported_pixel_formats);
   options_ = options;
   info_cb_ = std::move(info_cb);
   output_cb_ = std::move(output_cb);
@@ -539,16 +545,22 @@
       format == PIXEL_FORMAT_ABGR || format == PIXEL_FORMAT_ARGB;
   const bool supported_format =
       format == PIXEL_FORMAT_NV12 || format == PIXEL_FORMAT_I420 || is_rgb;
-  if (!supported_format) {
+  const bool is_gpu_frame =
+      first_frame->HasTextures() || first_frame->HasNativeGpuMemoryBuffer();
+  const bool is_gpu_supported_format =
+      is_gpu_frame &&
+      base::ranges::find(gpu_supported_pixel_formats_, format) !=
+          gpu_supported_pixel_formats_.end();
+  if (!supported_format && !is_gpu_supported_format) {
     InitCompleted(EncoderStatus(EncoderStatus::Codes::kUnsupportedFrameFormat,
                                 "Unexpected frame format.")
                       .WithData("frame", first_frame->AsHumanReadableString()));
     return;
   }
 
-  auto vea_config =
-      SetUpVeaConfig(profile_, options_, format, first_frame->storage_type(),
-                     supported_rc_modes_, required_encoder_type_);
+  auto vea_config = SetUpVeaConfig(
+      profile_, options_, format, first_frame->storage_type(),
+      supported_rc_modes_, required_encoder_type_, is_gpu_supported_format);
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   // Linux/ChromeOS require a special configuration to use dmabuf storage.
@@ -1155,9 +1167,12 @@
 
   const auto dest_coded_size = input_coded_size_;
   const auto dest_visible_rect = gfx::Rect(options_.frame_size);
+  bool is_gpu_supported_format =
+      base::ranges::find(gpu_supported_pixel_formats_, src_frame->format()) !=
+      gpu_supported_pixel_formats_.end();
 
   if (src_frame->HasMappableGpuBuffer() &&
-      src_frame->format() == PIXEL_FORMAT_NV12 &&
+      (src_frame->format() == PIXEL_FORMAT_NV12 || is_gpu_supported_format) &&
       (gpu_resize_supported_ || src_frame->coded_size() == dest_coded_size)) {
     // Nothing to do here, the input frame is already what we need
     return src_frame;
diff --git a/media/video/video_encode_accelerator_adapter.h b/media/video/video_encode_accelerator_adapter.h
index 276b1a70..67201f7f 100644
--- a/media/video/video_encode_accelerator_adapter.h
+++ b/media/video/video_encode_accelerator_adapter.h
@@ -188,6 +188,7 @@
   VideoCodecProfile profile_ = VIDEO_CODEC_PROFILE_UNKNOWN;
   VideoEncodeAccelerator::SupportedRateControlMode supported_rc_modes_ =
       VideoEncodeAccelerator::kNoMode;
+  std::vector<VideoPixelFormat> gpu_supported_pixel_formats_;
   Options options_;
   EncoderInfoCB info_cb_;
   OutputCB output_cb_;
diff --git a/net/cert/cert_verify_proc_ios.cc b/net/cert/cert_verify_proc_ios.cc
index df19430..e54b643 100644
--- a/net/cert/cert_verify_proc_ios.cc
+++ b/net/cert/cert_verify_proc_ios.cc
@@ -11,6 +11,7 @@
 #include "base/apple/foundation_util.h"
 #include "base/apple/osstatus_logging.h"
 #include "base/apple/scoped_cftyperef.h"
+#include "base/containers/span.h"
 #include "base/logging.h"
 #include "base/notreached.h"
 #include "crypto/sha2.h"
@@ -253,9 +254,7 @@
 
     std::string_view spki_bytes;
     if (!asn1::ExtractSPKIFromDERCert(
-            std::string_view(
-                reinterpret_cast<const char*>(CFDataGetBytePtr(der_data.get())),
-                CFDataGetLength(der_data.get())),
+            base::as_string_view(base::apple::CFDataToSpan(der_data.get())),
             &spki_bytes)) {
       verify_result->cert_status |= CERT_STATUS_INVALID;
       return;
diff --git a/net/cert/internal/trust_store_mac.cc b/net/cert/internal/trust_store_mac.cc
index 51d04d5b..f63b93d6 100644
--- a/net/cert/internal/trust_store_mac.cc
+++ b/net/cert/internal/trust_store_mac.cc
@@ -374,9 +374,8 @@
         LOG(ERROR) << "SecCertificateCopyData error";
         continue;
       }
-      auto buffer = x509_util::CreateCryptoBuffer(base::make_span(
-          CFDataGetBytePtr(der_data.get()),
-          base::checked_cast<size_t>(CFDataGetLength(der_data.get()))));
+      auto buffer = x509_util::CreateCryptoBuffer(
+          base::apple::CFDataToSpan(der_data.get()));
       bssl::CertErrors errors;
       bssl::ParseCertificateOptions options;
       options.allow_invalid_serial_numbers = true;
@@ -809,9 +808,8 @@
         LOG(ERROR) << "SecCertificateCopyData error";
         continue;
       }
-      auto buffer = x509_util::CreateCryptoBuffer(base::make_span(
-          CFDataGetBytePtr(der_data.get()),
-          base::checked_cast<size_t>(CFDataGetLength(der_data.get()))));
+      auto buffer = x509_util::CreateCryptoBuffer(
+          base::apple::CFDataToSpan(der_data.get()));
       bssl::CertErrors errors;
       bssl::ParseCertificateOptions options;
       options.allow_invalid_serial_numbers = true;
@@ -995,9 +993,8 @@
         LOG(ERROR) << "SecCertificateCopyData error";
         continue;
       }
-      auto buffer = x509_util::CreateCryptoBuffer(base::make_span(
-          CFDataGetBytePtr(der_data.get()),
-          base::checked_cast<size_t>(CFDataGetLength(der_data.get()))));
+      auto buffer = x509_util::CreateCryptoBuffer(
+          base::apple::CFDataToSpan(der_data.get()));
       bssl::CertErrors errors;
       bssl::ParseCertificateOptions options;
       options.allow_invalid_serial_numbers = true;
diff --git a/net/cert/x509_util_apple.cc b/net/cert/x509_util_apple.cc
index e6bae2d..8dc244d5 100644
--- a/net/cert/x509_util_apple.cc
+++ b/net/cert/x509_util_apple.cc
@@ -13,6 +13,7 @@
 
 #include <string>
 
+#include "base/apple/foundation_util.h"
 #include "base/check_op.h"
 #include "base/logging.h"
 #include "base/notreached.h"
@@ -37,9 +38,7 @@
   if (!der_data) {
     return nullptr;
   }
-  return CreateCryptoBuffer(base::make_span(
-      CFDataGetBytePtr(der_data.get()),
-      base::checked_cast<size_t>(CFDataGetLength(der_data.get()))));
+  return CreateCryptoBuffer(base::apple::CFDataToSpan(der_data.get()));
 }
 
 }  // namespace
diff --git a/net/cert/x509_util_apple_unittest.cc b/net/cert/x509_util_apple_unittest.cc
index eac1b3c..64546ef 100644
--- a/net/cert/x509_util_apple_unittest.cc
+++ b/net/cert/x509_util_apple_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <string_view>
 
+#include "base/apple/foundation_util.h"
 #include "base/containers/span.h"
 #include "build/build_config.h"
 #include "net/cert/x509_certificate.h"
@@ -28,9 +29,9 @@
     ADD_FAILURE();
     return result;
   }
-  result.assign(reinterpret_cast<const char*>(CFDataGetBytePtr(der_data.get())),
-                CFDataGetLength(der_data.get()));
-  return result;
+
+  return std::string(
+      base::as_string_view(base::apple::CFDataToSpan(der_data.get())));
 }
 
 std::string BytesForSecCert(const void* sec_cert) {
diff --git a/net/socket/tcp_socket_io_completion_port_win.cc b/net/socket/tcp_socket_io_completion_port_win.cc
index b6d38f25..dcd73c72 100644
--- a/net/socket/tcp_socket_io_completion_port_win.cc
+++ b/net/socket/tcp_socket_io_completion_port_win.cc
@@ -10,9 +10,11 @@
 #include "base/dcheck_is_on.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/message_loop/message_pump_win.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "base/numerics/checked_math.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/rand_util.h"
 #include "base/task/current_thread.h"
 #include "base/threading/thread_checker.h"
 #include "base/win/object_watcher.h"
@@ -26,6 +28,15 @@
 
 namespace {
 
+// Outcome of setting FILE_SKIP_COMPLETION_PORT_ON_SUCCESS on a socket. Used in
+// UMA histograms so should not be renumbered.
+enum class SkipCompletionPortOnSuccessOutcome {
+  kNotSupported,
+  kSetFileCompletionNotificationModesFailed,
+  kSuccess,
+  kMaxValue = kSuccess
+};
+
 bool g_skip_completion_port_on_success_enabled = true;
 
 // Returns true if all available transport protocols return Installable File
@@ -70,6 +81,17 @@
   return false;
 }
 
+// Returns true for 1/1000 calls, indicating if a subsampled histogram should be
+// recorded.
+bool ShouldRecordSubsampledHistogram() {
+  // Not using `base::MetricsSubSampler` because it's not thread-safe sockets
+  // could be used from multiple threads.
+  static std::atomic<uint64_t> counter = base::RandUint64();
+  // Relaxed memory order since there is no dependent memory access.
+  uint64_t val = counter.fetch_add(1, std::memory_order_relaxed);
+  return val % 1000 == 0;
+}
+
 class WSAEventHandleTraits {
  public:
   using Handle = WSAEVENT;
@@ -357,12 +379,15 @@
     return;
   }
 
+  // Register the `CoreImpl` as an I/O handler for the socket.
   CoreImpl& core = GetCoreImpl();
   auto hresult = base::CurrentIOThread::Get()->RegisterIOHandler(
       reinterpret_cast<HANDLE>(socket_), &core);
   CHECK(SUCCEEDED(hresult));
   registered_as_io_handler_ = true;
 
+  // Activate an option to skip the completion port when an operation completes
+  // immediately.
   static const bool skip_completion_port_on_success_is_supported =
       SkipCompletionPortOnSuccessIsSupported();
   if (g_skip_completion_port_on_success_enabled &&
@@ -372,6 +397,23 @@
         FILE_SKIP_COMPLETION_PORT_ON_SUCCESS);
     skip_completion_port_on_success_ = (result != 0);
   }
+
+  // Report the outcome of activating an option to skip the completion port when
+  // an operation completes immediately to UMA. Subsampled for efficiency.
+  if (ShouldRecordSubsampledHistogram()) {
+    SkipCompletionPortOnSuccessOutcome outcome;
+    if (skip_completion_port_on_success_) {
+      outcome = SkipCompletionPortOnSuccessOutcome::kSuccess;
+    } else if (skip_completion_port_on_success_is_supported) {
+      outcome = SkipCompletionPortOnSuccessOutcome::
+          kSetFileCompletionNotificationModesFailed;
+    } else {
+      outcome = SkipCompletionPortOnSuccessOutcome::kNotSupported;
+    }
+
+    base::UmaHistogramEnumeration(
+        "Net.Socket.SkipCompletionPortOnSuccessOutcome", outcome);
+  }
 }
 
 int TcpSocketIoCompletionPortWin::DidCompleteRead(
diff --git a/net/ssl/ssl_platform_key_mac.cc b/net/ssl/ssl_platform_key_mac.cc
index 281b8548..124b301 100644
--- a/net/ssl/ssl_platform_key_mac.cc
+++ b/net/ssl/ssl_platform_key_mac.cc
@@ -153,9 +153,8 @@
       return ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED;
     }
 
-    signature->assign(CFDataGetBytePtr(signature_ref.get()),
-                      CFDataGetBytePtr(signature_ref.get()) +
-                          CFDataGetLength(signature_ref.get()));
+    auto signature_span = base::apple::CFDataToSpan(signature_ref.get());
+    signature->assign(signature_span.begin(), signature_span.end());
     return OK;
   }
 
diff --git a/pdf/pdfium/pdfium_ink_writer_unittest.cc b/pdf/pdfium/pdfium_ink_writer_unittest.cc
index 92b7cb9..51a4f1a 100644
--- a/pdf/pdfium/pdfium_ink_writer_unittest.cc
+++ b/pdf/pdfium/pdfium_ink_writer_unittest.cc
@@ -19,6 +19,7 @@
 #include "pdf/pdfium/pdfium_engine_exports.h"
 #include "pdf/pdfium/pdfium_page.h"
 #include "pdf/pdfium/pdfium_test_base.h"
+#include "pdf/test/pdf_ink_test_helpers.h"
 #include "pdf/test/test_client.h"
 #include "pdf/test/test_helpers.h"
 #include "printing/units.h"
@@ -40,12 +41,7 @@
 
 constexpr PdfInkBrush::Params kBasicBrushParams = {SK_ColorRED, 4.0f};
 
-struct InputData {
-  gfx::PointF position;
-  base::TimeDelta time;
-};
-
-constexpr auto kBasicInputs = std::to_array<InputData>({
+constexpr auto kBasicInputs = std::to_array<PdfInkInputData>({
     {{126.122f, 52.852f}, base::Seconds(0.0f)},
     {{127.102f, 52.2398f}, base::Seconds(0.031467f)},
     {{130.041f, 50.7704f}, base::Seconds(0.07934f)},
@@ -83,7 +79,7 @@
 });
 
 std::optional<ink::StrokeInputBatch> CreateInputBatch(
-    base::span<const InputData> inputs) {
+    base::span<const PdfInkInputData> inputs) {
   ink::StrokeInputBatch input_batch;
   for (const auto& input : inputs) {
     auto result = input_batch.Append(CreateInkStrokeInput(
diff --git a/pdf/test/pdf_ink_test_helpers.h b/pdf/test/pdf_ink_test_helpers.h
index 4ac550c9..ef791d9 100644
--- a/pdf/test/pdf_ink_test_helpers.h
+++ b/pdf/test/pdf_ink_test_helpers.h
@@ -7,9 +7,11 @@
 
 #include <string>
 
+#include "base/time/time.h"
 #include "base/values.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/ink/src/ink/geometry/affine_transform.h"
+#include "ui/gfx/geometry/point_f.h"
 
 namespace chrome_pdf {
 
@@ -21,6 +23,13 @@
   int color_b;
 };
 
+// Used to generate ink::StrokeInput. Many tests may need both a `position` and
+// a `time` to consistently generate the same results.
+struct PdfInkInputData {
+  gfx::PointF position;
+  base::TimeDelta time;
+};
+
 base::Value::Dict CreateSetAnnotationModeMessageForTesting(bool enable);
 
 base::Value::Dict CreateSetAnnotationBrushMessageForTesting(
diff --git a/printing/printing_context_mac.mm b/printing/printing_context_mac.mm
index e724e46e..f39f8b2 100644
--- a/printing/printing_context_mac.mm
+++ b/printing/printing_context_mac.mm
@@ -112,39 +112,32 @@
 
 base::expected<std::vector<uint8_t>, mojom::ResultCode>
 CaptureSystemPrintSettings(PMPrintSettings& print_settings) {
-  CFDataRef data_ref = nullptr;
+  base::apple::ScopedCFTypeRef<CFDataRef> data_ref;
   OSStatus status = PMPrintSettingsCreateDataRepresentation(
-      print_settings, &data_ref, kPMDataFormatXMLDefault);
+      print_settings, data_ref.InitializeInto(), kPMDataFormatXMLDefault);
   if (status != noErr) {
     OSSTATUS_LOG(ERROR, status)
         << "Failed to create data representation of print settings";
     return base::unexpected(mojom::ResultCode::kFailed);
   }
 
-  base::apple::ScopedCFTypeRef<CFDataRef> scoped_data_ref(data_ref);
-  uint32_t data_size = CFDataGetLength(data_ref);
-  std::vector<uint8_t> capture_data(data_size);
-  CFDataGetBytes(data_ref, CFRangeMake(0, data_size),
-                 static_cast<UInt8*>(&capture_data.front()));
-  return capture_data;
+  auto data_span = base::apple::CFDataToSpan(data_ref.get());
+  return std::vector<uint8_t>(data_span.begin(), data_span.end());
 }
 
 base::expected<std::vector<uint8_t>, mojom::ResultCode> CaptureSystemPageFormat(
     PMPageFormat& page_format) {
-  CFDataRef data_ref = nullptr;
+  base::apple::ScopedCFTypeRef<CFDataRef> data_ref;
   OSStatus status = PMPageFormatCreateDataRepresentation(
-      page_format, &data_ref, kPMDataFormatXMLDefault);
+      page_format, data_ref.InitializeInto(), kPMDataFormatXMLDefault);
   if (status != noErr) {
     OSSTATUS_LOG(ERROR, status)
         << "Failed to create data representation of page format";
     return base::unexpected(mojom::ResultCode::kFailed);
   }
 
-  uint32_t data_size = CFDataGetLength(data_ref);
-  std::vector<uint8_t> capture_data(data_size);
-  CFDataGetBytes(data_ref, CFRangeMake(0, data_size),
-                 static_cast<UInt8*>(&capture_data.front()));
-  return capture_data;
+  auto data_span = base::apple::CFDataToSpan(data_ref.get());
+  return std::vector<uint8_t>(data_span.begin(), data_span.end());
 }
 
 base::expected<base::apple::ScopedCFTypeRef<CFStringRef>, mojom::ResultCode>
diff --git a/remoting/base/errors.cc b/remoting/base/errors.cc
index 50a81ef7..ea6d440 100644
--- a/remoting/base/errors.cc
+++ b/remoting/base/errors.cc
@@ -36,6 +36,8 @@
     {ErrorCode::UNAUTHORIZED_ACCOUNT, "UNAUTHORIZED_ACCOUNT"},
     {ErrorCode::REAUTHZ_POLICY_CHECK_FAILED, "REAUTHZ_POLICY_CHECK_FAILED"},
     {ErrorCode::NO_COMMON_AUTH_METHOD, "NO_COMMON_AUTH_METHOD"},
+    {ErrorCode::LOGIN_SCREEN_NOT_SUPPORTED, "LOGIN_SCREEN_NOT_SUPPORTED"},
+    {ErrorCode::SESSION_POLICIES_CHANGED, "SESSION_POLICIES_CHANGED"},
 };
 
 }  // namespace
@@ -96,6 +98,10 @@
       return proto::ErrorCode::REAUTHORIZATION_FAILED;
     case ErrorCode::NO_COMMON_AUTH_METHOD:
       return proto::ErrorCode::NO_COMMON_AUTH_METHOD;
+    case ErrorCode::LOGIN_SCREEN_NOT_SUPPORTED:
+      return proto::ErrorCode::LOGIN_SCREEN_NOT_SUPPORTED;
+    case ErrorCode::SESSION_POLICIES_CHANGED:
+      return proto::ErrorCode::SESSION_POLICIES_CHANGED;
   }
 }
 
diff --git a/remoting/base/errors.h b/remoting/base/errors.h
index 2b8a36a8..b6070f3 100644
--- a/remoting/base/errors.h
+++ b/remoting/base/errors.h
@@ -22,6 +22,9 @@
 //     ToExtendedStartCrdSessionResultCode, ToStartCrdSessionResultCode
 // * remoting/base/errors.cc: kErrorCodeNames
 // * remoting/host/mojom/desktop_session.mojom: ProtocolErrorCode
+// * remoting/host/mojom/remoting_mojom_traits.h:
+//     EnumTraits<remoting::mojom::ProtocolErrorCode,
+//                ::remoting::protocol::ErrorCode>
 // * tools/metrics/histograms/metadata/enterprise/enums.xml:
 //     EnterpriseCrdSessionResultCode
 //
@@ -51,7 +54,9 @@
   UNAUTHORIZED_ACCOUNT = 20,
   REAUTHZ_POLICY_CHECK_FAILED = 21,
   NO_COMMON_AUTH_METHOD = 22,
-  ERROR_CODE_MAX = NO_COMMON_AUTH_METHOD,
+  LOGIN_SCREEN_NOT_SUPPORTED = 23,
+  SESSION_POLICIES_CHANGED = 24,
+  ERROR_CODE_MAX = SESSION_POLICIES_CHANGED,
 };
 
 bool ParseErrorCode(const std::string& name, ErrorCode* result);
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index ad7fdc89..c6feeb1 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/chrome_build.gni")
 import("//build/config/chromeos/ui_mode.gni")
 import("//build/config/linux/pkg_config.gni")
 import("//build/util/process_version.gni")
@@ -50,7 +51,12 @@
 
 process_version("remoting_version") {
   template_file = "//remoting/host/version.h.in"
-  sources = [ branding_path ]
+  sources = [
+    branding_path,
+
+    # This is the Chrome branding, which includes "MAC_TEAM_ID".
+    branding_file_path,
+  ]
   output = "$target_gen_dir/version.h"
 }
 
@@ -241,6 +247,29 @@
   }
 }
 
+source_set("mojo_caller_security_checker") {
+  sources = [
+    "mojo_caller_security_checker.cc",
+    "mojo_caller_security_checker.h",
+  ]
+  deps = [
+    ":remoting_version",
+    "//base",
+    "//components/named_mojo_ipc_server",
+    "//remoting/host/base",
+  ]
+  if (is_mac) {
+    deps += [ "//remoting/host/mac:constants" ]
+    frameworks = [
+      "Security.framework",
+      "Carbon.framework",
+    ]
+  }
+  if (is_win) {
+    deps += [ "//remoting/host/win:trust_util" ]
+  }
+}
+
 # This must be a static library instead of a source set because
 # remoting_unittests requires that remoting_me2me_host.cc not be pulled in,
 # which in turn depends on remoting_me2me_host_static which isn't part of that
@@ -373,8 +402,6 @@
     "keyboard_layout_monitor.h",
     "me2me_heartbeat_service_client.cc",
     "me2me_heartbeat_service_client.h",
-    "mojo_caller_security_checker.cc",
-    "mojo_caller_security_checker.h",
     "mojo_video_capturer.cc",
     "mojo_video_capturer.h",
     "mojo_video_capturer_list.cc",
@@ -487,6 +514,7 @@
     ":common_headers",
     ":enterprise_params",
     ":host_extension",
+    ":mojo_caller_security_checker",
     "//ipc",
     "//remoting/host/base",
     "//remoting/proto",
@@ -945,6 +973,10 @@
     deps += [ "//remoting/host/linux:unit_tests" ]
   }
 
+  if (is_mac) {
+    deps += [ "//remoting/host/mac:unit_tests" ]
+  }
+
   if (remoting_use_x11) {
     sources += [ "x11_display_util_unittest.cc" ]
     deps += [
@@ -1188,7 +1220,10 @@
     }
 
     if (is_mac) {
-      deps += [ "//remoting/host/mac:permission_checking" ]
+      deps += [
+        "//remoting/host/mac:agent_process_broker_client",
+        "//remoting/host/mac:permission_checking",
+      ]
     }
   }
 
diff --git a/remoting/host/base/host_exit_codes.cc b/remoting/host/base/host_exit_codes.cc
index 9bb69e5..0ac6280 100644
--- a/remoting/host/base/host_exit_codes.cc
+++ b/remoting/host/base/host_exit_codes.cc
@@ -17,7 +17,7 @@
     {kInvalidHostIdExitCode, "INVALID_HOST_ID"},
     {kInvalidOauthCredentialsExitCode, "INVALID_OAUTH_CREDENTIALS"},
     {kInvalidHostDomainExitCode, "INVALID_HOST_DOMAIN"},
-    {kLoginScreenNotSupportedExitCode, "LOGIN_SCREEN_NOT_SUPPORTED"},
+    {kTerminatedByAgentProcessBroker, "TERMINATED_BY_AGENT_PROCESS_BROKER"},
     {kUsernameMismatchExitCode, "USERNAME_MISMATCH"},
     {kHostDeletedExitCode, "HOST_DELETED"},
     {kRemoteAccessDisallowedExitCode, "REMOTE_ACCESS_DISALLOWED"},
diff --git a/remoting/host/base/host_exit_codes.h b/remoting/host/base/host_exit_codes.h
index 5a07822..aff1b508 100644
--- a/remoting/host/base/host_exit_codes.h
+++ b/remoting/host/base/host_exit_codes.h
@@ -24,7 +24,7 @@
   kInvalidHostIdExitCode = 101,
   kInvalidOauthCredentialsExitCode = 102,
   kInvalidHostDomainExitCode = 103,
-  kLoginScreenNotSupportedExitCode = 104,
+  kTerminatedByAgentProcessBroker = 104,
   kUsernameMismatchExitCode = 105,
   kHostDeletedExitCode = 106,
   kRemoteAccessDisallowedExitCode = 107,
diff --git a/remoting/host/client_session.cc b/remoting/host/client_session.cc
index e9ff33b..dc747c4f 100644
--- a/remoting/host/client_session.cc
+++ b/remoting/host/client_session.cc
@@ -547,6 +547,9 @@
 
   DesktopEnvironmentOptions options = desktop_environment_options_;
   options.ApplySessionOptions(session_options);
+  if (effective_policies_.curtain_required.has_value()) {
+    options.set_enable_curtaining(*effective_policies_.curtain_required);
+  }
   // Create the desktop environment. Drop the connection if it could not be
   // created for any reason (for instance the curtain could not initialize).
   desktop_environment_ = desktop_environment_factory_->Create(
@@ -1013,9 +1016,7 @@
     const SessionPolicies& new_policies) {
   DCHECK(local_session_policy_update_subscription_);
   HOST_LOG << "Effective policies have changed. Terminating session.";
-  // TODO: crbug.com/359977809 - create a new error code for session policy
-  // changed.
-  DisconnectSession(ErrorCode::HOST_CONFIGURATION_ERROR);
+  DisconnectSession(ErrorCode::SESSION_POLICIES_CHANGED);
 }
 
 void ClientSession::OnVideoSizeChanged(protocol::VideoStream* video_stream,
diff --git a/remoting/host/curtain_mode_mac.cc b/remoting/host/curtain_mode_mac.cc
index 188a632..da5dbef 100644
--- a/remoting/host/curtain_mode_mac.cc
+++ b/remoting/host/curtain_mode_mac.cc
@@ -17,8 +17,8 @@
 #include "base/mac/login_util.h"
 #include "base/memory/ptr_util.h"
 #include "base/task/single_thread_task_runner.h"
+#include "remoting/base/errors.h"
 #include "remoting/host/client_session_control.h"
-#include "remoting/protocol/errors.h"
 
 namespace remoting {
 
@@ -88,7 +88,7 @@
   void RemoveEventHandler();
 
   // Disconnects the client session.
-  void DisconnectSession(protocol::ErrorCode error);
+  void DisconnectSession(ErrorCode error);
 
   // Handlers for the switch-in event.
   static OSStatus SessionActivateHandler(EventHandlerCallRef handler,
@@ -140,11 +140,27 @@
 }
 
 void SessionWatcher::ActivateCurtain() {
+  if (getuid() == 0) {
+    // When curtain mode is in effect on Mac, the host process runs in the
+    // user's switched-out session, but launchd will also run an instance at
+    // the console login screen.  Even if no user is currently logged-on, we
+    // can't support remote-access to the login screen because the current host
+    // process model disconnects the client during login, which would leave
+    // the logged in session un-curtained on the console until they reconnect.
+    //
+    // In case of fast user switch, there will be two host processes running,
+    // one as the logged on user and another one as root. AgentProcessBroker
+    // will terminate the root host process in that case.
+    LOG(ERROR)
+        << "Connecting to the console login session is not yet supported.";
+    DisconnectSession(ErrorCode::LOGIN_SCREEN_NOT_SUPPORTED);
+    return;
+  }
   // Try to install the switch-in handler. Do this before switching out the
   // current session so that the console session is not affected if it fails.
   if (!InstallEventHandler()) {
     LOG(ERROR) << "Failed to install the switch-in handler.";
-    DisconnectSession(protocol::ErrorCode::HOST_CONFIGURATION_ERROR);
+    DisconnectSession(ErrorCode::HOST_CONFIGURATION_ERROR);
     return;
   }
 
@@ -179,12 +195,12 @@
       // Disconnect the session since we are unable to enter curtain mode.
       LOG(ERROR) << "SACSwitchToLoginWindow unavailable - unable to enter "
                     "curtain mode.";
-      DisconnectSession(protocol::ErrorCode::HOST_CONFIGURATION_ERROR);
+      DisconnectSession(ErrorCode::HOST_CONFIGURATION_ERROR);
       return;
     }
     if (err.value() != noErr) {
       OSSTATUS_LOG(ERROR, err.value()) << "Failed to switch to login window";
-      DisconnectSession(protocol::ErrorCode::HOST_CONFIGURATION_ERROR);
+      DisconnectSession(ErrorCode::HOST_CONFIGURATION_ERROR);
       return;
     }
     if (is_headless) {
@@ -195,7 +211,7 @@
       LOG(ERROR) << "Machine is running in headless mode (no monitors "
                  << "attached), we attempted to curtain the session but "
                  << "SACSwitchToLoginWindow is likely to fail in this mode.";
-      DisconnectSession(protocol::ErrorCode::HOST_CONFIGURATION_ERROR);
+      DisconnectSession(ErrorCode::HOST_CONFIGURATION_ERROR);
       return;
     }
   }
@@ -213,7 +229,7 @@
       &event_handler_);
   if (result != noErr) {
     event_handler_ = nullptr;
-    DisconnectSession(protocol::ErrorCode::HOST_CONFIGURATION_ERROR);
+    DisconnectSession(ErrorCode::HOST_CONFIGURATION_ERROR);
     return false;
   }
 
@@ -229,7 +245,7 @@
   }
 }
 
-void SessionWatcher::DisconnectSession(protocol::ErrorCode error) {
+void SessionWatcher::DisconnectSession(ErrorCode error) {
   if (!caller_task_runner_->BelongsToCurrentThread()) {
     caller_task_runner_->PostTask(
         FROM_HERE,
@@ -245,8 +261,7 @@
 OSStatus SessionWatcher::SessionActivateHandler(EventHandlerCallRef handler,
                                                 EventRef event,
                                                 void* user_data) {
-  static_cast<SessionWatcher*>(user_data)->DisconnectSession(
-      protocol::ErrorCode::OK);
+  static_cast<SessionWatcher*>(user_data)->DisconnectSession(ErrorCode::OK);
   return noErr;
 }
 
diff --git a/remoting/host/host_main.cc b/remoting/host/host_main.cc
index 6956d92..a0f1df3 100644
--- a/remoting/host/host_main.cc
+++ b/remoting/host/host_main.cc
@@ -260,9 +260,12 @@
 
   mojo::core::Init({
 #if BUILDFLAG(IS_WIN)
-    .is_broker_process = main_routine == &DaemonProcessMain
+      .is_broker_process = main_routine == &DaemonProcessMain
+#elif BUILDFLAG(IS_MAC)
+      // The broker process on Mac is the agent process broker.
+      .is_broker_process = false
 #else
-    .is_broker_process = main_routine == &HostProcessMain
+      .is_broker_process = main_routine == &HostProcessMain
 #endif
   });
 
diff --git a/remoting/host/installer/mac/BUILD.gn b/remoting/host/installer/mac/BUILD.gn
index 0b39eeb..78aee1b 100644
--- a/remoting/host/installer/mac/BUILD.gn
+++ b/remoting/host/installer/mac/BUILD.gn
@@ -15,6 +15,7 @@
     "ChromotingHostService.pkgproj",
     "ChromotingHostUninstaller.pkgproj",
     "LaunchAgents/org.chromium.chromoting.plist",
+    "LaunchDaemons/org.chromium.chromoting.broker.plist",
     "Scripts/keystone_install.sh",
     "Scripts/remoting_postflight.sh",
     "Scripts/remoting_preflight.sh",
diff --git a/remoting/host/installer/mac/ChromotingHostService.pkgproj b/remoting/host/installer/mac/ChromotingHostService.pkgproj
index b4f2a33..ddd557d 100644
--- a/remoting/host/installer/mac/ChromotingHostService.pkgproj
+++ b/remoting/host/installer/mac/ChromotingHostService.pkgproj
@@ -313,7 +313,24 @@
 							</dict>
 							<dict>
 								<key>CHILDREN</key>
-								<array/>
+								<array>
+									<dict>
+										<key>CHILDREN</key>
+										<array/>
+										<key>GID</key>
+										<integer>0</integer>
+										<key>PATH</key>
+										<string>LaunchDaemons/org.chromium.chromoting.broker.plist</string>
+										<key>PATH_TYPE</key>
+										<integer>1</integer>
+										<key>PERMISSIONS</key>
+										<integer>420</integer>
+										<key>TYPE</key>
+										<integer>3</integer>
+										<key>UID</key>
+										<integer>0</integer>
+									</dict>
+								</array>
 								<key>GID</key>
 								<integer>0</integer>
 								<key>PATH</key>
diff --git a/remoting/host/installer/mac/LaunchDaemons/org.chromium.chromoting.broker.plist b/remoting/host/installer/mac/LaunchDaemons/org.chromium.chromoting.broker.plist
new file mode 100644
index 0000000..9cc06a62
--- /dev/null
+++ b/remoting/host/installer/mac/LaunchDaemons/org.chromium.chromoting.broker.plist
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>Disabled</key>
+  <false/>
+  <key>Label</key>
+  <string>org.chromium.chromoting.broker</string>
+  <key>ProgramArguments</key>
+  <array>
+    <string>/Library/PrivilegedHelperTools/@@HOST_BUNDLE_NAME@@/Contents/MacOS/remoting_agent_process_broker</string>
+  </array>
+	<key>MachServices</key>
+  <dict>
+    <key>chromoting.agent_process_broker_mojo_ipc</key>
+    <true/>
+  </dict>
+</dict>
+</plist>
diff --git a/remoting/host/installer/mac/Scripts/remoting_postflight.sh b/remoting/host/installer/mac/Scripts/remoting_postflight.sh
index 83510e3..abe3409 100755
--- a/remoting/host/installer/mac/Scripts/remoting_postflight.sh
+++ b/remoting/host/installer/mac/Scripts/remoting_postflight.sh
@@ -11,6 +11,7 @@
 CONFIG_FILE="$HELPERTOOLS/$SERVICE_NAME.json"
 OLD_SCRIPT_FILE="$HELPERTOOLS/$SERVICE_NAME.me2me.sh"
 PLIST=/Library/LaunchAgents/org.chromium.chromoting.plist
+BROKER_PLIST=/Library/LaunchDaemons/org.chromium.chromoting.broker.plist
 PAM_CONFIG=/etc/pam.d/chrome-remote-desktop
 ENABLED_FILE="$HELPERTOOLS/$SERVICE_NAME.me2me_enabled"
 ENABLED_FILE_BACKUP="$ENABLED_FILE.backup"
@@ -131,6 +132,9 @@
 fi
 
 if [[ -r "$USERS_TMP_FILE" ]]; then
+  logger Starting broker service
+  launchctl load -w $BROKER_PLIST
+
   for uid in $(sort "$USERS_TMP_FILE" | uniq); do
     logger Starting service for user "$uid".
 
diff --git a/remoting/host/installer/mac/Scripts/remoting_preflight.sh b/remoting/host/installer/mac/Scripts/remoting_preflight.sh
index 5ad7fa8..7560c82 100755
--- a/remoting/host/installer/mac/Scripts/remoting_preflight.sh
+++ b/remoting/host/installer/mac/Scripts/remoting_preflight.sh
@@ -14,6 +14,7 @@
 HOST_SERVICE_BINARY="$HELPERTOOLS/$HOST_BUNDLE_NAME/Contents/MacOS/remoting_me2me_host_service"
 USERS_TMP_FILE="$HOST_SERVICE_BINARY.users"
 PLIST=/Library/LaunchAgents/org.chromium.chromoting.plist
+BROKER_PLIST=/Library/LaunchDaemons/org.chromium.chromoting.broker.plist
 ENABLED_FILE="$HELPERTOOLS/$SERVICE_NAME.me2me_enabled"
 ENABLED_FILE_BACKUP="$ENABLED_FILE.backup"
 PREF_PANE=/Library/PreferencePanes/ChromeRemoteDesktop.prefPane
@@ -63,8 +64,8 @@
 
 logger Running Chrome Remote Desktop preflight script @@VERSION@@
 
-# If there is an _enabled file, rename it while upgrading.
 if [[ -f "$ENABLED_FILE" ]]; then
+  # If there is an _enabled file, rename it while upgrading.
   logger Moving _enabled file
   mv "$ENABLED_FILE" "$ENABLED_FILE_BACKUP"
 fi
@@ -79,6 +80,9 @@
   cp "$OLD_SCRIPT_FILE" "$INSTALLER_TEMP/script_backup"
 fi
 
+logger Unloading broker service
+launchctl unload -w $BROKER_PLIST
+
 # Stop and unload the service for each user currently running the service, and
 # record the user IDs so the service can be restarted for the same users in the
 # postflight script.
diff --git a/remoting/host/installer/mac/do_signing.sh b/remoting/host/installer/mac/do_signing.sh
index 4283e11..9068890 100755
--- a/remoting/host/installer/mac/do_signing.sh
+++ b/remoting/host/installer/mac/do_signing.sh
@@ -40,6 +40,7 @@
   # Binaries/bundles to sign.
   ME2ME_HOST="PrivilegedHelperTools/${HOST_BUNDLE_NAME}"
   ME2ME_EXE_DIR="${ME2ME_HOST}/Contents/MacOS/"
+  ME2ME_AGENT_PROCESS_BROKER="${ME2ME_EXE_DIR}/remoting_agent_process_broker"
   ME2ME_LAUNCHD_SERVICE="${ME2ME_EXE_DIR}/remoting_me2me_host_service"
   ME2ME_NM_HOST="${ME2ME_EXE_DIR}/${NATIVE_MESSAGING_HOST_BUNDLE_NAME}/"
   IT2ME_NM_HOST="${ME2ME_EXE_DIR}/${REMOTE_ASSISTANCE_HOST_BUNDLE_NAME}/"
@@ -152,6 +153,7 @@
   local id="${3}"
 
   local binaries=(\
+    "${ME2ME_AGENT_PROCESS_BROKER}" \
     "${ME2ME_LAUNCHD_SERVICE}" \
     "${ME2ME_NM_HOST}" \
     "${IT2ME_NM_HOST}" \
diff --git a/remoting/host/installer/mac/uninstaller/remoting_uninstaller.mm b/remoting/host/installer/mac/uninstaller/remoting_uninstaller.mm
index 3d4af9c..86e7344 100644
--- a/remoting/host/installer/mac/uninstaller/remoting_uninstaller.mm
+++ b/remoting/host/installer/mac/uninstaller/remoting_uninstaller.mm
@@ -99,7 +99,7 @@
   [self sudoCommand:"/bin/rm" withArguments:args usingAuth:authRef];
 }
 
-- (void)shutdownService {
+- (void)shutdownServiceUsingAuth:(AuthorizationRef)authRef {
   const char* launchCtl = "/bin/launchctl";
   const char* argsStop[] = { "stop", remoting::kServiceName, nullptr };
   [self runCommand:launchCtl withArguments:argsStop];
@@ -110,6 +110,13 @@
                                 remoting::kServicePlistPath, nullptr };
     [self runCommand:launchCtl withArguments:argsUnload];
   }
+
+  if ([NSFileManager.defaultManager
+          fileExistsAtPath:@(remoting::kBrokerPlistPath)]) {
+    const char* argsUnload[] = {"unload", "-w", remoting::kBrokerPlistPath,
+                                nullptr};
+    [self sudoCommand:launchCtl withArguments:argsUnload usingAuth:authRef];
+  }
 }
 
 - (void)keystoneUnregisterUsingAuth:(AuthorizationRef)authRef {
@@ -143,9 +150,10 @@
   // restart itself.
   [self sudoDelete:remoting::kHostEnabledPath usingAuth:authRef];
 
-  [self shutdownService];
+  [self shutdownServiceUsingAuth:authRef];
 
   [self sudoDelete:remoting::kServicePlistPath usingAuth:authRef];
+  [self sudoDelete:remoting::kBrokerPlistPath usingAuth:authRef];
   [self sudoDelete:remoting::kHostBinaryPath usingAuth:authRef];
   [self sudoDelete:remoting::kHostLegacyBinaryPath usingAuth:authRef];
   [self sudoDelete:remoting::kOldHostHelperScriptPath usingAuth:authRef];
diff --git a/remoting/host/ipc_constants.cc b/remoting/host/ipc_constants.cc
index 194a2d3..c0174023 100644
--- a/remoting/host/ipc_constants.cc
+++ b/remoting/host/ipc_constants.cc
@@ -35,6 +35,19 @@
     "chromoting.host_services_mojo_ipc";
 #endif
 
+#if BUILDFLAG(IS_MAC)
+
+#if !defined(NDEBUG)
+constexpr char kAgentProcessBrokerIpcName[] =
+    "chromoting.agent_process_broker_debug_mojo_ipc";
+#else
+// Must match the `MachServices` key in org.chromium.chromoting.broker.plist.
+constexpr char kAgentProcessBrokerIpcName[] =
+    "chromoting.agent_process_broker_mojo_ipc";
+#endif
+
+#endif
+
 }  // namespace
 
 const base::FilePath::CharType kHostBinaryName[] =
@@ -84,4 +97,19 @@
   return *server_name;
 }
 
+#if BUILDFLAG(IS_MAC)
+
+const char kAgentProcessBrokerMessagePipeId[] = "agent-process-broker";
+
+const mojo::NamedPlatformChannel::ServerName&
+GetAgentProcessBrokerServerName() {
+  static const base::NoDestructor<mojo::NamedPlatformChannel::ServerName>
+      server_name(
+          named_mojo_ipc_server::WorkingDirectoryIndependentServerNameFromUTF8(
+              kAgentProcessBrokerIpcName));
+  return *server_name;
+}
+
+#endif
+
 }  // namespace remoting
diff --git a/remoting/host/ipc_constants.h b/remoting/host/ipc_constants.h
index 8436c0c..5357b33 100644
--- a/remoting/host/ipc_constants.h
+++ b/remoting/host/ipc_constants.h
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include "base/files/file_path.h"
+#include "build/buildflag.h"
 #include "mojo/public/cpp/platform/named_platform_channel.h"
 
 namespace remoting {
@@ -18,10 +19,7 @@
 // Name of the desktop process binary.
 extern const base::FilePath::CharType kDesktopBinaryName[];
 
-// Message pipe ID used for ChromotingHostServices. Currently only used on
-// Linux.
-// TODO(crbug.com/40244097): Make Windows hosts work with non-isolated
-// connections.
+// Message pipe ID used for ChromotingHostServices.
 extern const uint64_t kChromotingHostServicesMessagePipeId;
 
 // Returns the full path to an installed |binary| in |full_path|.
@@ -32,6 +30,14 @@
 const mojo::NamedPlatformChannel::ServerName&
 GetChromotingHostServicesServerName();
 
+#if BUILDFLAG(IS_MAC)
+// Message pipe ID used for AgentProcessBroker.
+extern const char kAgentProcessBrokerMessagePipeId[];
+
+// Returns the server name for AgentProcessBroker.
+const mojo::NamedPlatformChannel::ServerName& GetAgentProcessBrokerServerName();
+#endif
+
 }  // namespace remoting
 
 #endif  // REMOTING_HOST_IPC_CONSTANTS_H_
diff --git a/remoting/host/it2me/it2me_host.cc b/remoting/host/it2me/it2me_host.cc
index 72a8cafa..32217aa 100644
--- a/remoting/host/it2me/it2me_host.cc
+++ b/remoting/host/it2me/it2me_host.cc
@@ -330,8 +330,6 @@
         !chrome_os_enterprise_params_->suppress_notifications);
     options.set_terminate_upon_input(
         chrome_os_enterprise_params_->terminate_upon_input);
-    options.set_enable_curtaining(
-        chrome_os_enterprise_params_->curtain_local_user_session);
   }
 #endif
 
diff --git a/remoting/host/it2me/it2me_host_unittest.cc b/remoting/host/it2me/it2me_host_unittest.cc
index 98e9358c..339557e 100644
--- a/remoting/host/it2me/it2me_host_unittest.cc
+++ b/remoting/host/it2me/it2me_host_unittest.cc
@@ -916,13 +916,13 @@
 TEST_F(It2MeHostTest, ConnectRespectsEnableCurtainingParameter) {
   StartHost(ChromeOsEnterpriseParams{.curtain_local_user_session = true});
 
-  EXPECT_TRUE(GetHost()->desktop_environment_options().enable_curtaining());
+  EXPECT_TRUE(*get_local_session_policies().curtain_required);
 }
 
 TEST_F(It2MeHostTest, EnableCurtainingDefaultsToFalse) {
   StartHost(/*enterprise_params=*/std::nullopt);
 
-  EXPECT_FALSE(GetHost()->desktop_environment_options().enable_curtaining());
+  EXPECT_FALSE(get_local_session_policies().curtain_required.has_value());
 }
 
 TEST_F(It2MeHostTest, AllowEnterpriseFileTransferWithPolicyEnabled) {
diff --git a/remoting/host/mac/BUILD.gn b/remoting/host/mac/BUILD.gn
index e5dfd4d..5084c36 100644
--- a/remoting/host/mac/BUILD.gn
+++ b/remoting/host/mac/BUILD.gn
@@ -25,6 +25,7 @@
   defines = [
     "HOST_BUNDLE_NAME=\"" + me2me_host_bundle_name + "\"",
     "HOST_LEGACY_BUNDLE_NAME=\"" + me2me_host_legacy_bundle_name + "\"",
+    "HOST_BUNDLE_ID=\"" + host_bundle_id + "\"",
   ]
 
   sources = [
@@ -59,6 +60,20 @@
   frameworks = [ "AVFoundation.framework" ]
 }
 
+source_set("agent_process_broker_client") {
+  sources = [
+    "agent_process_broker_client.cc",
+    "agent_process_broker_client.h",
+  ]
+  deps = [
+    "//base",
+    "//components/named_mojo_ipc_server",
+    "//mojo/public/cpp/platform",
+    "//remoting/host:ipc_constants",
+    "//remoting/host/mojom",
+  ]
+}
+
 executable("remoting_me2me_host_service") {
   sources = [ "host_service_main.cc" ]
   configs += [ "//remoting/build/config:version" ]
@@ -77,6 +92,41 @@
   public_deps = [ ":remoting_me2me_host_service" ]
 }
 
+source_set("agent_process_broker") {
+  sources = [
+    "agent_process_broker.cc",
+    "agent_process_broker.h",
+  ]
+  deps = [
+    "//base",
+    "//components/named_mojo_ipc_server",
+    "//mojo/public/cpp/platform",
+    "//remoting/base",
+    "//remoting/host:ipc_constants",
+    "//remoting/host:mojo_caller_security_checker",
+    "//remoting/host/base",
+    "//remoting/host/mojom",
+  ]
+}
+
+executable("remoting_agent_process_broker") {
+  sources = [ "agent_process_broker_main.cc" ]
+  configs += [ "//remoting/build/config:version" ]
+  deps = [
+    ":agent_process_broker",
+    "//base",
+    "//mojo/core/embedder",
+    "//remoting/base",
+    "//remoting/base:logging",
+  ]
+}
+
+bundle_data("remoting_agent_process_broker_bundle_data") {
+  sources = [ "$root_out_dir/remoting_agent_process_broker" ]
+  outputs = [ "{{bundle_executable_dir}}/{{source_file_part}}" ]
+  public_deps = [ ":remoting_agent_process_broker" ]
+}
+
 # remoting_me2me_host-InfoPlist.strings
 foreach(locale, remoting_locales_with_underscores) {
   bundle_data("remoting_me2me_host_strings_${locale}_bundle_data") {
@@ -127,6 +177,7 @@
     ]
   }
   deps += [
+    ":remoting_agent_process_broker_bundle_data",
     ":remoting_host_resources",
     ":remoting_me2me_host_service_bundle_data",
     "//remoting/host:remoting_infoplist_strings",
@@ -213,3 +264,21 @@
       [ "VERSION=" + "$chrome_version_major" + "." + "$remoting_version_patch" +
         "." + "$chrome_version_build" + "." + "$chrome_version_patch" ]
 }
+
+source_set("unit_tests") {
+  testonly = true
+
+  sources = [ "agent_process_broker_unittest.cc" ]
+
+  deps = [
+    ":agent_process_broker",
+    ":agent_process_broker_client",
+    "//base",
+    "//base/test:test_support",
+    "//components/named_mojo_ipc_server:named_mojo_ipc_server",
+    "//mojo/public/cpp/platform",
+    "//remoting/host/mojom",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/remoting/host/mac/agent_process_broker.cc b/remoting/host/mac/agent_process_broker.cc
new file mode 100644
index 0000000..30fa5db
--- /dev/null
+++ b/remoting/host/mac/agent_process_broker.cc
@@ -0,0 +1,200 @@
+// 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 "remoting/host/mac/agent_process_broker.h"
+
+#include <mach/message.h>
+#include <stddef.h>
+#include <sys/sysctl.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/process/process_handle.h"
+#include "base/sequence_checker.h"
+#include "base/strings/stringprintf.h"
+#include "components/named_mojo_ipc_server/connection_info.h"
+#include "components/named_mojo_ipc_server/endpoint_options.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "remoting/base/logging.h"
+#include "remoting/host/ipc_constants.h"
+#include "remoting/host/mojo_caller_security_checker.h"
+#include "remoting/host/mojom/agent_process_broker.mojom.h"
+
+namespace remoting {
+
+namespace {
+
+bool IsRootProcess(audit_token_t audit_token) {
+  return audit_token_to_ruid(audit_token) == 0;
+}
+
+}  // namespace
+
+AgentProcessBroker::AgentProcess::AgentProcess(
+    size_t reference_id,
+    base::ProcessId pid,
+    mojo::Remote<mojom::AgentProcess> remote,
+    bool is_root,
+    bool is_active)
+    : reference_id(reference_id),
+      pid(pid),
+      remote(std::move(remote)),
+      is_root(is_root),
+      is_active(is_active) {}
+AgentProcessBroker::AgentProcess::AgentProcess(AgentProcess&&) = default;
+AgentProcessBroker::AgentProcess::~AgentProcess() = default;
+AgentProcessBroker::AgentProcess& AgentProcessBroker::AgentProcess::operator=(
+    AgentProcess&&) = default;
+
+void AgentProcessBroker::AgentProcess::ResumeProcess() {
+  if (is_active) {
+    return;
+  }
+  remote->ResumeProcess();
+  is_active = true;
+  HOST_LOG << GetAgentProcessLogString("resumed");
+}
+
+void AgentProcessBroker::AgentProcess::SuspendProcess() {
+  if (!is_active) {
+    return;
+  }
+  remote->SuspendProcess();
+  is_active = false;
+  HOST_LOG << GetAgentProcessLogString("suspended");
+}
+
+std::string AgentProcessBroker::AgentProcess::GetAgentProcessLogString(
+    std::string_view state) const {
+  return base::StringPrintf("Agent process %d (PID: %d, %s) %s", reference_id,
+                            pid, is_root ? "root" : "user", state.data());
+}
+
+AgentProcessBroker::AgentProcessBroker()
+    : AgentProcessBroker(GetAgentProcessBrokerServerName(),
+                         base::BindRepeating(IsTrustedMojoEndpoint),
+                         base::BindRepeating(IsRootProcess)) {}
+
+AgentProcessBroker::AgentProcessBroker(
+    const mojo::NamedPlatformChannel::ServerName& server_name,
+    Validator validator,
+    IsRootProcessGetter is_root_process)
+    : server_(named_mojo_ipc_server::EndpointOptions(
+                  server_name,
+                  kAgentProcessBrokerMessagePipeId),
+              validator.Then(base::BindRepeating(
+                  [](mojom::AgentProcessBroker* interface, bool is_valid) {
+                    return is_valid ? interface : nullptr;
+                  },
+                  this))),
+      is_root_process_(std::move(is_root_process)) {}
+
+AgentProcessBroker::~AgentProcessBroker() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void AgentProcessBroker::Start() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  server_.StartServer();
+  HOST_LOG << "Agent process broker has started.";
+}
+
+void AgentProcessBroker::OnAgentProcessLaunched(
+    mojo::PendingRemote<mojom::AgentProcess> pending_agent_process) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  auto& connection_info = server_.current_connection_info();
+  bool is_root = is_root_process_.Run(connection_info.audit_token);
+  mojo::Remote<mojom::AgentProcess> process_remote{
+      std::move(pending_agent_process)};
+  process_remote.set_disconnect_handler(
+      base::BindOnce(&AgentProcessBroker::OnAgentProcessDisconnected,
+                     base::Unretained(this), next_reference_id_));
+  auto result = agent_processes_.emplace(
+      next_reference_id_, AgentProcess{next_reference_id_, connection_info.pid,
+                                       std::move(process_remote), is_root,
+                                       /* is_active= */ false});
+  DCHECK(result.second);  // Assert success.
+  HOST_LOG << result.first->second.GetAgentProcessLogString("launched");
+  next_reference_id_++;
+  BrokerAgentProcesses();
+  if (on_agent_process_launched_) {
+    std::move(on_agent_process_launched_).Run();
+  }
+}
+
+void AgentProcessBroker::OnAgentProcessDisconnected(size_t reference_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  auto it = agent_processes_.find(reference_id);
+  if (it != agent_processes_.end()) {
+    HOST_LOG << it->second.GetAgentProcessLogString("disconnected");
+    agent_processes_.erase(it);
+    BrokerAgentProcesses();
+  } else {
+    LOG(WARNING) << "Agent process ID " << reference_id << " not found.";
+  }
+}
+
+void AgentProcessBroker::BrokerAgentProcesses() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Goal: There can be up to one root process and one user process running.
+  // When both the root process and the user process are running, the root
+  // process must be suspended to give way to the user process.
+  std::vector<AgentProcess*> root_processes;
+  std::vector<AgentProcess*> user_processes;
+  for (auto& pair : agent_processes_) {
+    if (pair.second.is_root) {
+      root_processes.push_back(&pair.second);
+    } else {
+      user_processes.push_back(&pair.second);
+    }
+  }
+  TrimProcessList(root_processes);
+  TrimProcessList(user_processes);
+  if (!user_processes.empty()) {
+    // Suspend the root process if it's running, then resume the user process,
+    // since the latter has higher priority.
+    if (!root_processes.empty()) {
+      root_processes.front()->SuspendProcess();
+    }
+    user_processes.front()->ResumeProcess();
+    return;
+  }
+  if (!root_processes.empty()) {
+    // There are no user processes, so resume the root process.
+    root_processes.front()->ResumeProcess();
+  }
+}
+
+void AgentProcessBroker::TrimProcessList(
+    std::vector<AgentProcess*>& processes) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (processes.size() < 2) {
+    return;
+  }
+  // Sort processes. Active processes come first so that they are the last to be
+  // removed.
+  std::sort(processes.begin(), processes.end(),
+            [](const AgentProcess* p1, const AgentProcess* p2) {
+              return p1->is_active && !p2->is_active;
+            });
+  while (processes.size() > 1) {
+    const AgentProcess* process = processes.back();
+    HOST_LOG << process->GetAgentProcessLogString("closed");
+    // This destroys the remote, which triggers the disconnect callback on the
+    // client and terminates the process.
+    // Note: this will invalidate the storage that `processes.back()` references
+    // to.
+    agent_processes_.erase(process->reference_id);
+    processes.pop_back();
+  }
+}
+
+}  // namespace remoting
diff --git a/remoting/host/mac/agent_process_broker.h b/remoting/host/mac/agent_process_broker.h
new file mode 100644
index 0000000..48280c0
--- /dev/null
+++ b/remoting/host/mac/agent_process_broker.h
@@ -0,0 +1,105 @@
+// 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 REMOTING_HOST_MAC_AGENT_PROCESS_BROKER_H_
+#define REMOTING_HOST_MAC_AGENT_PROCESS_BROKER_H_
+
+#include <mach/message.h>
+
+#include <memory>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/functional/callback.h"
+#include "base/functional/callback_forward.h"
+#include "base/process/process_handle.h"
+#include "base/sequence_checker.h"
+#include "components/named_mojo_ipc_server/named_mojo_ipc_server.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+#include "remoting/host/mojom/agent_process_broker.mojom.h"
+
+namespace remoting {
+
+class AgentProcessBroker final : public mojom::AgentProcessBroker {
+ public:
+  AgentProcessBroker();
+  AgentProcessBroker(const AgentProcessBroker&) = delete;
+  AgentProcessBroker& operator=(const AgentProcessBroker&) = delete;
+  ~AgentProcessBroker() override;
+
+  void Start();
+
+  void OnAgentProcessLaunched(
+      mojo::PendingRemote<mojom::AgentProcess> pending_agent_process) override;
+
+ private:
+  friend class AgentProcessBrokerTest;
+
+  struct AgentProcess {
+    AgentProcess(size_t reference_id,
+                 base::ProcessId pid,
+                 mojo::Remote<mojom::AgentProcess> remote,
+                 bool is_root,
+                 bool is_active);
+    AgentProcess(AgentProcess&&);
+    ~AgentProcess();
+
+    AgentProcess& operator=(AgentProcess&&);
+
+    void ResumeProcess();
+    void SuspendProcess();
+
+    std::string GetAgentProcessLogString(std::string_view state) const;
+
+    size_t reference_id;  // For reverse lookup in `agent_processes_`.
+    base::ProcessId pid;  // For logging only. Not for book keeping.
+    mojo::Remote<mojom::AgentProcess> remote;
+    bool is_root;
+    bool is_active;
+  };
+
+  using Validator = base::RepeatingCallback<bool(
+      const named_mojo_ipc_server::ConnectionInfo&)>;
+
+  // Interface to allow tests to fake the root-ness/non-root-ness of a process,
+  // since tests can't launch process as root.
+  using IsRootProcessGetter = base::RepeatingCallback<bool(audit_token_t)>;
+
+  AgentProcessBroker(const mojo::NamedPlatformChannel::ServerName& server_name,
+                     Validator validator,
+                     IsRootProcessGetter is_root_process);
+
+  void OnAgentProcessDisconnected(size_t reference_id);
+  void BrokerAgentProcesses();
+
+  // Closes all processes except one. If there are active processes in
+  // |processes|, then the one not being closed is guaranteed to be active.
+  // Other than that, the process being left over is arbitrary.
+  void TrimProcessList(std::vector<AgentProcess*>& processes);
+
+  void set_on_agent_process_launched_for_testing(
+      base::OnceClosure on_agent_process_launched) {
+    on_agent_process_launched_ = std::move(on_agent_process_launched);
+  }
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  named_mojo_ipc_server::NamedMojoIpcServer<mojom::AgentProcessBroker> server_;
+  IsRootProcessGetter is_root_process_;
+  base::flat_map<size_t /* reference_id */, AgentProcess> agent_processes_;
+  // We use our own reference ID for book keeping. While unlikely, the OS is
+  // free to immediately reuse the PID after a process has exited. This might
+  // cause race conditions since the disconnect callback might be called after
+  // the new process is spawned.
+  size_t next_reference_id_ = 0u;
+  base::OnceClosure on_agent_process_launched_;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_MAC_AGENT_PROCESS_BROKER_H_
diff --git a/remoting/host/mac/agent_process_broker_client.cc b/remoting/host/mac/agent_process_broker_client.cc
new file mode 100644
index 0000000..6672728
--- /dev/null
+++ b/remoting/host/mac/agent_process_broker_client.cc
@@ -0,0 +1,95 @@
+// 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 "remoting/host/mac/agent_process_broker_client.h"
+
+#include <memory>
+
+#include "base/functional/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.h"
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+#include "mojo/public/cpp/system/invitation.h"
+#include "remoting/host/ipc_constants.h"
+#include "remoting/host/mojom/agent_process_broker.mojom.h"
+
+namespace remoting {
+
+AgentProcessBrokerClient::AgentProcessBrokerClient(
+    base::OnceClosure on_disconnected)
+    : on_disconnected_(std::move(on_disconnected)) {}
+
+AgentProcessBrokerClient::~AgentProcessBrokerClient() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+bool AgentProcessBrokerClient::ConnectToServer() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return ConnectToServer(GetAgentProcessBrokerServerName());
+}
+
+bool AgentProcessBrokerClient::ConnectToServer(
+    const mojo::NamedPlatformChannel::ServerName& server_name) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  auto endpoint = named_mojo_ipc_server::ConnectToServer(server_name);
+  if (!endpoint.is_valid()) {
+    LOG(WARNING) << "Cannot connect to IPC through server name " << server_name
+                 << ". Endpoint is invalid.";
+    // This may happen if the broker process is somehow launched after the host
+    // process. The host process will exit and the host service process will
+    // relaunch the host process.
+    return false;
+  }
+  auto invitation = mojo::IncomingInvitation::Accept(std::move(endpoint));
+  auto message_pipe =
+      invitation.ExtractMessagePipe(kAgentProcessBrokerMessagePipeId);
+  mojo::PendingRemote<mojom::AgentProcessBroker> pending_remote(
+      std::move(message_pipe), /* version= */ 0);
+  if (!pending_remote.is_valid()) {
+    LOG(WARNING) << "Invalid message pipe.";
+    return false;
+  }
+  broker_remote_.Bind(std::move(pending_remote));
+  broker_remote_.set_disconnect_handler(base::BindOnce(
+      &AgentProcessBrokerClient::OnBrokerDisconnected, base::Unretained(this)));
+  return true;
+}
+
+void AgentProcessBrokerClient::OnAgentProcessLaunched(
+    mojom::AgentProcess* agent_process) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(broker_remote_.is_bound());
+  DCHECK(!agent_process_receiver_);
+  agent_process_receiver_ =
+      std::make_unique<mojo::Receiver<mojom::AgentProcess>>(agent_process);
+  broker_remote_->OnAgentProcessLaunched(
+      agent_process_receiver_->BindNewPipeAndPassRemote());
+  agent_process_receiver_->set_disconnect_handler(base::BindOnce(
+      &AgentProcessBrokerClient::OnAgentProcessRemoteDisconnected,
+      base::Unretained(this)));
+}
+
+void AgentProcessBrokerClient::OnBrokerDisconnected() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  LOG(ERROR) << "Broker process has disconnected.";
+  // This may happen if the broker process has crashed. The host process will
+  // exit and the host service process will relaunch it.
+  RunDisconnectedCallback();
+}
+
+void AgentProcessBrokerClient::OnAgentProcessRemoteDisconnected() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  LOG(WARNING) << "Agent process remote has disconnected.";
+  RunDisconnectedCallback();
+}
+
+void AgentProcessBrokerClient::RunDisconnectedCallback() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (on_disconnected_) {
+    std::move(on_disconnected_).Run();
+  }
+}
+
+}  // namespace remoting
diff --git a/remoting/host/mac/agent_process_broker_client.h b/remoting/host/mac/agent_process_broker_client.h
new file mode 100644
index 0000000..ce8ef4e
--- /dev/null
+++ b/remoting/host/mac/agent_process_broker_client.h
@@ -0,0 +1,45 @@
+// 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 REMOTING_HOST_MAC_AGENT_PROCESS_BROKER_CLIENT_H_
+#define REMOTING_HOST_MAC_AGENT_PROCESS_BROKER_CLIENT_H_
+
+#include <memory>
+
+#include "base/functional/callback.h"
+#include "base/sequence_checker.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+#include "remoting/host/mojom/agent_process_broker.mojom.h"
+
+namespace remoting {
+
+class AgentProcessBrokerClient final {
+ public:
+  explicit AgentProcessBrokerClient(base::OnceClosure on_disconnected);
+  AgentProcessBrokerClient(const AgentProcessBrokerClient&) = delete;
+  AgentProcessBrokerClient& operator=(const AgentProcessBrokerClient&) = delete;
+  ~AgentProcessBrokerClient();
+
+  bool ConnectToServer();
+  bool ConnectToServer(
+      const mojo::NamedPlatformChannel::ServerName& server_name);
+  void OnAgentProcessLaunched(mojom::AgentProcess* agent_process);
+
+ private:
+  void OnBrokerDisconnected();
+  void OnAgentProcessRemoteDisconnected();
+  void RunDisconnectedCallback();
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::OnceClosure on_disconnected_;
+  mojo::Remote<mojom::AgentProcessBroker> broker_remote_;
+  std::unique_ptr<mojo::Receiver<mojom::AgentProcess>> agent_process_receiver_;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_MAC_AGENT_PROCESS_BROKER_CLIENT_H_
diff --git a/remoting/host/mac/agent_process_broker_main.cc b/remoting/host/mac/agent_process_broker_main.cc
new file mode 100644
index 0000000..fda57dd
--- /dev/null
+++ b/remoting/host/mac/agent_process_broker_main.cc
@@ -0,0 +1,35 @@
+// 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 "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/run_loop.h"
+#include "base/task/single_thread_task_executor.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/task/thread_pool/thread_pool_instance.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/core/embedder/scoped_ipc_support.h"
+#include "remoting/base/logging.h"
+#include "remoting/host/mac/agent_process_broker.h"
+
+int main(int argc, char const* argv[]) {
+  base::AtExitManager exitManager;
+  base::CommandLine::Init(argc, argv);
+  remoting::InitHostLogging();
+  base::ThreadPoolInstance::CreateAndStartWithDefaultParams(
+      "AgentProcessBroker");
+  base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::IO);
+  auto task_runner = base::SingleThreadTaskRunner::GetCurrentDefault();
+  mojo::core::Init({
+      .is_broker_process = true,
+  });
+  mojo::core::ScopedIPCSupport ipc_support(
+      task_runner, mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST);
+
+  remoting::AgentProcessBroker agent_process_broker;
+  base::RunLoop run_loop;
+  agent_process_broker.Start();
+  run_loop.Run();
+  return 0;
+}
diff --git a/remoting/host/mac/agent_process_broker_unittest.cc b/remoting/host/mac/agent_process_broker_unittest.cc
new file mode 100644
index 0000000..d9a8b2f
--- /dev/null
+++ b/remoting/host/mac/agent_process_broker_unittest.cc
@@ -0,0 +1,307 @@
+// 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 "remoting/host/mac/agent_process_broker.h"
+
+#include <inttypes.h>
+
+#include <algorithm>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/containers/span.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_forward.h"
+#include "base/memory/ptr_util.h"
+#include "base/process/process.h"
+#include "base/rand_util.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/bind.h"
+#include "base/test/mock_callback.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "components/named_mojo_ipc_server/connection_info.h"
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+#include "remoting/host/mac/agent_process_broker_client.h"
+#include "remoting/host/mojom/agent_process_broker.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+namespace remoting {
+
+namespace {
+
+using testing::_;
+using testing::Return;
+
+static constexpr char kRemotingTestAgentProcessName[] =
+    "RemotingTestAgentProcess";
+
+static constexpr char kServerNameSwitch[] = "server-name";
+static constexpr char kAgentStateFilePathSwitch[] = "state-file";
+
+static constexpr char kAgentStateAwaiting[] = "awaiting";
+static constexpr char kAgentStateResumed[] = "resumed";
+static constexpr char kAgentStateSuspended[] = "suspended";
+
+// A struct that holds both the real process object and the path of the agent
+// state file.
+struct Process {
+  base::Process process;
+  base::FilePath agent_state_file_path;
+};
+
+// A test AgentProcess implementation that simply writes state changes to
+// `agent_state_file_path`. It will immediately write `kAgentStateAwaiting`
+// when the object is constructed.
+class TestAgentProcess : public mojom::AgentProcess {
+ public:
+  explicit TestAgentProcess(const base::FilePath& agent_state_file_path);
+  ~TestAgentProcess() override;
+
+  void ResumeProcess() override;
+  void SuspendProcess() override;
+
+ private:
+  void WriteAgentState(std::string_view state);
+
+  base::File agent_state_file_;
+};
+
+TestAgentProcess::TestAgentProcess(
+    const base::FilePath& agent_state_file_path) {
+  agent_state_file_ = base::File(
+      agent_state_file_path, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+  EXPECT_TRUE(agent_state_file_.IsValid());
+  WriteAgentState(kAgentStateAwaiting);
+}
+
+TestAgentProcess::~TestAgentProcess() = default;
+
+void TestAgentProcess::ResumeProcess() {
+  WriteAgentState(kAgentStateResumed);
+}
+
+void TestAgentProcess::SuspendProcess() {
+  WriteAgentState(kAgentStateSuspended);
+}
+
+void TestAgentProcess::WriteAgentState(std::string_view state) {
+  agent_state_file_.SetLength(state.size());
+  agent_state_file_.Write(0, base::as_byte_span(state));
+  agent_state_file_.Flush();
+}
+
+}  // namespace
+
+class AgentProcessBrokerTest : public testing::Test {
+ public:
+  AgentProcessBrokerTest();
+  ~AgentProcessBrokerTest() override;
+
+ protected:
+  Process LaunchTestAgentProcess(bool is_root);
+  // Returns nullopt if the process has exited.
+  std::optional<std::string> GetTestAgentState(const Process& process);
+  // Returns false if the process has exited.
+  bool WaitForTestAgentState(const Process& process, std::string_view state);
+
+  base::MockCallback<AgentProcessBroker::IsRootProcessGetter>
+      is_root_process_getter_;
+  std::unique_ptr<AgentProcessBroker> agent_process_broker_;
+
+ private:
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::MainThreadType::IO};
+  mojo::NamedPlatformChannel::ServerName server_name_;
+  base::ScopedTempDir temp_dir_;
+};
+
+AgentProcessBrokerTest::AgentProcessBrokerTest() {
+  EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+  server_name_ = mojo::NamedPlatformChannel::ServerNameFromUTF8(
+      base::StringPrintf("remoting_agent_process_broker_test_server.%" PRIu64,
+                         base::RandUint64()));
+  agent_process_broker_ = base::WrapUnique(new AgentProcessBroker(
+      server_name_,
+      base::BindRepeating(
+          [](const named_mojo_ipc_server::ConnectionInfo&) { return true; }),
+      is_root_process_getter_.Get()));
+  agent_process_broker_->Start();
+}
+
+AgentProcessBrokerTest::~AgentProcessBrokerTest() = default;
+
+Process AgentProcessBrokerTest::LaunchTestAgentProcess(bool is_root) {
+  base::FilePath agent_state_file_path;
+  EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.GetPath(),
+                                             &agent_state_file_path));
+  EXPECT_CALL(is_root_process_getter_, Run(_)).WillOnce(Return(is_root));
+  base::CommandLine cmd_line = base::GetMultiProcessTestChildBaseCommandLine();
+  cmd_line.AppendSwitchNative(kServerNameSwitch, server_name_);
+  cmd_line.AppendSwitchPath(kAgentStateFilePathSwitch, agent_state_file_path);
+  base::RunLoop run_loop;
+  agent_process_broker_->set_on_agent_process_launched_for_testing(
+      run_loop.QuitClosure());
+  base::Process process = base::SpawnMultiProcessTestChild(
+      kRemotingTestAgentProcessName, cmd_line, /* options= */ {});
+  run_loop.Run();
+  return {
+      .process = std::move(process),
+      .agent_state_file_path = agent_state_file_path,
+  };
+}
+
+std::optional<std::string> AgentProcessBrokerTest::GetTestAgentState(
+    const Process& process) {
+  if (process.process.WaitForExitWithTimeout(base::TimeDelta(), nullptr)) {
+    // Process has exited.
+    return std::nullopt;
+  }
+  base::File file(process.agent_state_file_path,
+                  base::File::FLAG_OPEN | base::File::FLAG_READ);
+  std::vector<char> buffer(
+      std::max({sizeof(kAgentStateAwaiting), sizeof(kAgentStateResumed),
+                sizeof(kAgentStateSuspended)}));
+  std::optional<size_t> num_bytes_read =
+      file.Read(0, base::as_writable_byte_span(buffer));
+  if (!num_bytes_read.has_value()) {
+    return std::nullopt;
+  }
+  return std::string(buffer.data(), *num_bytes_read);
+}
+
+bool AgentProcessBrokerTest::WaitForTestAgentState(const Process& process,
+                                                   std::string_view state) {
+  base::RunLoop run_loop;
+  bool result;
+  base::RepeatingClosure quit_loop_on_state = base::BindLambdaForTesting([&]() {
+    std::optional<std::string> agent_state = GetTestAgentState(process);
+    if (!agent_state.has_value() || *agent_state == state) {
+      result = agent_state.has_value();
+      run_loop.Quit();
+      return;
+    }
+    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+        FROM_HERE, quit_loop_on_state, base::Milliseconds(10));
+  });
+  quit_loop_on_state.Run();
+  run_loop.Run();
+  return result;
+}
+
+TEST_F(AgentProcessBrokerTest, UserAgentProcessOnly_ResumedImmediately) {
+  auto process = LaunchTestAgentProcess(/* is_root= */ false);
+  ASSERT_TRUE(WaitForTestAgentState(process, kAgentStateResumed));
+}
+
+TEST_F(AgentProcessBrokerTest, RootAgentProcessOnly_ResumedImmediately) {
+  auto process = LaunchTestAgentProcess(/* is_root= */ true);
+  ASSERT_TRUE(WaitForTestAgentState(process, kAgentStateResumed));
+}
+
+TEST_F(AgentProcessBrokerTest,
+       UserAgentProcessCloseAndRelaunch_ResumedImmediately) {
+  auto p1 = LaunchTestAgentProcess(/* is_root= */ false);
+  ASSERT_TRUE(WaitForTestAgentState(p1, kAgentStateResumed));
+  ASSERT_TRUE(p1.process.Terminate(0, true));
+
+  auto p2 = LaunchTestAgentProcess(/* is_root= */ false);
+  ASSERT_TRUE(WaitForTestAgentState(p2, kAgentStateResumed));
+}
+
+TEST_F(AgentProcessBrokerTest,
+       RootAgentProcessCloseAndRelaunch_ResumedImmediately) {
+  auto p1 = LaunchTestAgentProcess(/* is_root= */ true);
+  ASSERT_TRUE(WaitForTestAgentState(p1, kAgentStateResumed));
+  ASSERT_TRUE(p1.process.Terminate(0, true));
+
+  auto p2 = LaunchTestAgentProcess(/* is_root= */ true);
+  ASSERT_TRUE(WaitForTestAgentState(p2, kAgentStateResumed));
+}
+
+TEST_F(AgentProcessBrokerTest,
+       UserAgentProcessAfterUserAgentProcess_SecondProcessClosedImmediately) {
+  auto p1 = LaunchTestAgentProcess(/* is_root= */ false);
+  ASSERT_TRUE(WaitForTestAgentState(p1, kAgentStateResumed));
+
+  auto p2 = LaunchTestAgentProcess(/* is_root= */ false);
+  ASSERT_TRUE(p2.process.WaitForExit(nullptr));
+}
+
+TEST_F(AgentProcessBrokerTest,
+       RootAgentProcessAfterRootAgentProcess_SecondProcessClosedImmediately) {
+  auto p1 = LaunchTestAgentProcess(/* is_root= */ true);
+  ASSERT_TRUE(WaitForTestAgentState(p1, kAgentStateResumed));
+
+  auto p2 = LaunchTestAgentProcess(/* is_root= */ true);
+  ASSERT_TRUE(p2.process.WaitForExit(nullptr));
+}
+
+TEST_F(AgentProcessBrokerTest,
+       UserAgentProcessAfterRootAgentProcess_RootProcessSuspended) {
+  auto root_process = LaunchTestAgentProcess(/* is_root= */ true);
+  ASSERT_TRUE(WaitForTestAgentState(root_process, kAgentStateResumed));
+
+  auto user_process = LaunchTestAgentProcess(/* is_root= */ false);
+  ASSERT_TRUE(WaitForTestAgentState(user_process, kAgentStateResumed));
+  ASSERT_TRUE(WaitForTestAgentState(root_process, kAgentStateSuspended));
+}
+
+TEST_F(
+    AgentProcessBrokerTest,
+    RootAgentProcessAfterUserAgentProcess_RootProcessResumedAfterUserProcessExited) {
+  auto user_process = LaunchTestAgentProcess(/* is_root= */ false);
+  ASSERT_TRUE(WaitForTestAgentState(user_process, kAgentStateResumed));
+
+  auto root_process = LaunchTestAgentProcess(/* is_root= */ true);
+  ASSERT_TRUE(WaitForTestAgentState(root_process, kAgentStateAwaiting));
+
+  user_process.process.Terminate(0, true);
+  ASSERT_TRUE(WaitForTestAgentState(root_process, kAgentStateResumed));
+}
+
+TEST_F(AgentProcessBrokerTest, DestroyServer_TerminatesClientProcesses) {
+  auto user_process = LaunchTestAgentProcess(/* is_root= */ false);
+  ASSERT_TRUE(WaitForTestAgentState(user_process, kAgentStateResumed));
+
+  auto root_process = LaunchTestAgentProcess(/* is_root= */ true);
+  ASSERT_TRUE(WaitForTestAgentState(root_process, kAgentStateAwaiting));
+
+  agent_process_broker_.reset();
+
+  ASSERT_TRUE(user_process.process.WaitForExit(nullptr));
+  ASSERT_TRUE(root_process.process.WaitForExit(nullptr));
+}
+
+MULTIPROCESS_TEST_MAIN(RemotingTestAgentProcess) {
+  base::test::TaskEnvironment task_environment{
+      base::test::TaskEnvironment::MainThreadType::IO};
+  base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
+  mojo::NamedPlatformChannel::ServerName server_name =
+      cmd_line->GetSwitchValueNative(kServerNameSwitch);
+  base::RunLoop run_loop;
+  AgentProcessBrokerClient broker_client(run_loop.QuitClosure());
+  EXPECT_TRUE(broker_client.ConnectToServer(server_name));
+  base::FilePath state_file_path =
+      cmd_line->GetSwitchValuePath(kAgentStateFilePathSwitch);
+  TestAgentProcess test_process(state_file_path);
+  broker_client.OnAgentProcessLaunched(&test_process);
+  run_loop.Run();
+  return 0;
+}
+
+}  // namespace remoting
diff --git a/remoting/host/mac/constants_mac.cc b/remoting/host/mac/constants_mac.cc
index 13edb1c..afc04f90 100644
--- a/remoting/host/mac/constants_mac.cc
+++ b/remoting/host/mac/constants_mac.cc
@@ -9,14 +9,18 @@
 namespace remoting {
 
 #define SERVICE_NAME "org.chromium.chromoting"
+#define BROKER_NAME SERVICE_NAME ".broker"
 
 #define APPLICATIONS_DIR "/Applications/"
 #define HELPER_TOOLS_DIR "/Library/PrivilegedHelperTools/"
 #define LAUNCH_AGENTS_DIR "/Library/LaunchAgents/"
+#define LAUNCH_DAEMONS_DIR "/Library/LaunchDaemons/"
 #define LOG_DIR "/var/log/"
 #define LOG_CONFIG_DIR "/etc/newsyslog.d/"
 
+const char kBundleId[] = HOST_BUNDLE_ID;
 const char kServiceName[] = SERVICE_NAME;
+const char kBrokerName[] = BROKER_NAME;
 
 const char kHostConfigFileName[] = SERVICE_NAME ".json";
 const char kHostConfigFilePath[] = HELPER_TOOLS_DIR SERVICE_NAME ".json";
@@ -36,6 +40,8 @@
 
 const char kServicePlistPath[] = LAUNCH_AGENTS_DIR SERVICE_NAME ".plist";
 
+const char kBrokerPlistPath[] = LAUNCH_DAEMONS_DIR BROKER_NAME ".plist";
+
 const char kLogFilePath[] = LOG_DIR SERVICE_NAME ".log";
 const char kLogFileConfigPath[] = LOG_CONFIG_DIR SERVICE_NAME ".conf";
 
diff --git a/remoting/host/mac/constants_mac.h b/remoting/host/mac/constants_mac.h
index 0ddeb04..1bc0732 100644
--- a/remoting/host/mac/constants_mac.h
+++ b/remoting/host/mac/constants_mac.h
@@ -7,9 +7,15 @@
 
 namespace remoting {
 
+// The bundle ID for the Remoting Host.
+extern const char kBundleId[];
+
 // The name of the Remoting Host service that is registered with launchd.
 extern const char kServiceName[];
 
+// The name of the Remoting Host broker that is registered with launchd.
+extern const char kBrokerName[];
+
 // Use a single configuration file, instead of separate "auth" and "host" files.
 // This is because the SetConfigAndStart() API only provides a single
 // dictionary, and splitting this into two dictionaries would require
@@ -43,6 +49,9 @@
 // The .plist file for the Chromoting service.
 extern const char kServicePlistPath[];
 
+// The .plist file for the Chromoting agent broker.
+extern const char kBrokerPlistPath[];
+
 // Path to the host log file
 extern const char kLogFilePath[];
 
diff --git a/remoting/host/mac/host_service_main.cc b/remoting/host/mac/host_service_main.cc
index edc79b86..836d98b 100644
--- a/remoting/host/mac/host_service_main.cc
+++ b/remoting/host/mac/host_service_main.cc
@@ -177,6 +177,7 @@
           LOG(ERROR) << "Too many host failures. Giving up.";
           return 1;
         }
+        // TODO: crbug.com/366071356 - use exponential backoff
         base::TimeDelta relaunch_in = kMinimumRelaunchInterval - host_lifetime;
         HOST_LOG << "Relaunching in " << relaunch_in;
         base::PlatformThread::Sleep(relaunch_in);
diff --git a/remoting/host/mojo_caller_security_checker.cc b/remoting/host/mojo_caller_security_checker.cc
index cd76719..19de3f3 100644
--- a/remoting/host/mojo_caller_security_checker.cc
+++ b/remoting/host/mojo_caller_security_checker.cc
@@ -11,12 +11,25 @@
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/no_destructor.h"
+#include "base/notreached.h"
 #include "base/process/process_handle.h"
 #include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
 #include "build/build_config.h"
 #include "components/named_mojo_ipc_server/connection_info.h"
 #include "remoting/host/base/process_util.h"
 
+#if BUILDFLAG(IS_MAC)
+#include <Security/Security.h>
+
+#include "base/apple/osstatus_logging.h"
+#include "base/apple/scoped_cftyperef.h"
+#include "base/mac/code_signature.h"
+#include "base/strings/stringprintf.h"
+#include "remoting/host/mac/constants_mac.h"
+#include "remoting/host/version.h"
+#endif
+
 #if BUILDFLAG(IS_WIN)
 #include "remoting/host/win/trust_util.h"
 #endif
@@ -24,23 +37,71 @@
 namespace remoting {
 namespace {
 
+#if BUILDFLAG(IS_LINUX)
 constexpr auto kAllowedCallerProgramNames =
     base::MakeFixedFlatSet<base::FilePath::StringPieceType>({
-#if BUILDFLAG(IS_LINUX)
-      "remote-open-url", "remote-webauthn",
-#elif BUILDFLAG(IS_WIN)
-      L"remote_open_url.exe", L"remote_webauthn.exe",
-          L"remote_security_key.exe",
-#else
-      // MakeFixedFlatSet() requires at least one element.
-      "unsupported",
-#endif
+        "remote-open-url",
+        "remote-webauthn",
     });
+#elif BUILDFLAG(IS_WIN)
+constexpr auto kAllowedCallerProgramNames =
+    base::MakeFixedFlatSet<base::FilePath::StringPieceType>({
+        L"remote_open_url.exe",
+        L"remote_webauthn.exe",
+        L"remote_security_key.exe",
+    });
+#endif
 
 }  // namespace
 
 bool IsTrustedMojoEndpoint(
     const named_mojo_ipc_server::ConnectionInfo& caller) {
+#if BUILDFLAG(IS_MAC)
+
+#if defined(OFFICIAL_BUILD)
+  std::string requirement_string = base::StringPrintf(
+      // Certificate was issued by Apple
+      "anchor apple generic and "
+      // It's Google's certificate
+      "certificate leaf[subject.OU] = \"%s\" and "
+      // See:
+      // https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements#Xcode-designated-requirement-for-Developer-ID-code
+      "certificate 1[field.1.2.840.113635.100.6.2.6] and "
+      "certificate leaf[field.1.2.840.113635.100.6.1.13] and "
+      // For Chrome Remote Desktop
+      "identifier \"%s\"",
+      MAC_TEAM_ID, kBundleId);
+  base::apple::ScopedCFTypeRef<SecRequirementRef> requirement;
+  OSStatus status = SecRequirementCreateWithString(
+      base::SysUTF8ToCFStringRef(requirement_string).get(), kSecCSDefaultFlags,
+      requirement.InitializeInto());
+  if (status != errSecSuccess) {
+    OSSTATUS_LOG(ERROR, status)
+        << "Failed to create security requirement for string: "
+        << requirement_string;
+    return false;
+  }
+  status = base::mac::ProcessIsSignedAndFulfillsRequirement(caller.audit_token,
+                                                            requirement.get());
+  if (status == errSecSuccess) {
+    return true;
+  }
+  if (status == errSecCSReqFailed) {
+    OSSTATUS_LOG(ERROR, status) << "Security requirement unsatisfied";
+  } else {
+    OSSTATUS_LOG(ERROR, status)
+        << "Unknown error occurred when verifying security requirements";
+  }
+  return false;
+#else
+  // Skip codesign verification to allow for local development.
+  return true;
+#endif
+
+#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
+
+  // TODO: yuweih - see if it's possible to move away from PID-based security
+  // checks, which might be susceptible of PID reuse attacks.
   static base::NoDestructor<base::FilePath> current_process_image_path(
       GetProcessImagePath(base::GetCurrentProcId()));
   base::FilePath caller_process_image_path = GetProcessImagePath(caller.pid);
@@ -81,6 +142,10 @@
   // Linux binaries are not code-signed, so we just return true.
   return true;
 #endif
+
+#else  // Unsupported platform
+  NOTREACHED();
+#endif
 }
 
 }  // namespace remoting
diff --git a/remoting/host/mojom/BUILD.gn b/remoting/host/mojom/BUILD.gn
index 692c404..afa681f 100644
--- a/remoting/host/mojom/BUILD.gn
+++ b/remoting/host/mojom/BUILD.gn
@@ -6,6 +6,7 @@
 
 mojom("mojom") {
   sources = [
+    "agent_process_broker.mojom",
     "chromoting_host_services.mojom",
     "desktop_session.mojom",
     "keyboard_layout.mojom",
diff --git a/remoting/host/mojom/agent_process_broker.mojom b/remoting/host/mojom/agent_process_broker.mojom
new file mode 100644
index 0000000..2ad5812
--- /dev/null
+++ b/remoting/host/mojom/agent_process_broker.mojom
@@ -0,0 +1,52 @@
+// 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.
+
+module remoting.mojom;
+
+// Interface for the agent process broker to control the agent process. See
+// documentation on AgentProcessBroker below for more details.
+[EnableIf=is_mac]
+interface AgentProcess {
+  // Resumes the agent process. After calling this, the agent will be allowed to
+  // send heartbeats and connect to signaling.
+  ResumeProcess();
+
+  // Suspends the agent process. After calling this, the agent will stop sending
+  // heartbeats and disconnect from signaling.
+  SuspendProcess();
+};
+
+// Interface for brokering the agent processes. CRD hosts are run as Mac launch
+// agents, meaning they will be launched whenever a new user session (or the
+// login screen) has started. This will cause problems since the CRD
+// architecture does not support multiple hosts heartbeating and signaling with
+// the same directory registration. The agent process broker is used to ensure
+// there will be at most one host running at a time. The agent process broker is
+// run as a Mac launch daemon, meaning there will only be one process running as
+// root.
+//
+// See Apple's documentation about launch daemons and agents:
+// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html
+[EnableIf=is_mac]
+interface AgentProcessBroker {
+  // Called by an agent process right after it is launched. There will be three
+  // possible outcomes:
+  //
+  // 1. Broker closes the remote: agent should terminate immediately.
+  // 2. ResumeProcess() is called: agent is allowed to send heartbeats and
+  //    connect to signaling.
+  // 3. SuspendProcess() is called: agent should stop sending heartbeats and
+  //    disconnect from signaling, wait for ResumeProcess() to be called or
+  //    the remote to be closed.
+  //
+  // Right after the agent process is launched, it should not send heartbeats or
+  // connect to signaling until ResumeProcess() is called.
+  //
+  // Note: The broker does not wait for the exit/suspension of the agent
+  // process, so said process might heartbeat after the new agent process is
+  // resumed. This is not a problem with the current signaling implementation
+  // since all agent processes share the same signaling credential/registration.
+  // This could become problematic if the signaling mechanism is changed.
+  OnAgentProcessLaunched(pending_remote<AgentProcess> agent_process);
+};
diff --git a/remoting/host/mojom/desktop_session.mojom b/remoting/host/mojom/desktop_session.mojom
index ae0f6e23..f4de33c 100644
--- a/remoting/host/mojom/desktop_session.mojom
+++ b/remoting/host/mojom/desktop_session.mojom
@@ -566,6 +566,8 @@
   kUnauthorizedAccount = 20,
   kReauthzPolicyCheckFailed = 21,
   kNoCommonAuthMethod = 22,
+  kLoginScreenNotSupported = 23,
+  kSessionPoliciesChanged = 24,
 };
 
 // Provides the desktop integration process with a mechanism for controlling the
diff --git a/remoting/host/mojom/remoting_mojom_traits.h b/remoting/host/mojom/remoting_mojom_traits.h
index 28afd46..d41d089 100644
--- a/remoting/host/mojom/remoting_mojom_traits.h
+++ b/remoting/host/mojom/remoting_mojom_traits.h
@@ -1480,6 +1480,10 @@
         return remoting::mojom::ProtocolErrorCode::kReauthzPolicyCheckFailed;
       case ::remoting::protocol::ErrorCode::NO_COMMON_AUTH_METHOD:
         return remoting::mojom::ProtocolErrorCode::kNoCommonAuthMethod;
+      case ::remoting::protocol::ErrorCode::LOGIN_SCREEN_NOT_SUPPORTED:
+        return remoting::mojom::ProtocolErrorCode::kLoginScreenNotSupported;
+      case ::remoting::protocol::ErrorCode::SESSION_POLICIES_CHANGED:
+        return remoting::mojom::ProtocolErrorCode::kSessionPoliciesChanged;
     }
 
     NOTREACHED();
@@ -1558,6 +1562,12 @@
       case remoting::mojom::ProtocolErrorCode::kNoCommonAuthMethod:
         *out = ::remoting::protocol::ErrorCode::NO_COMMON_AUTH_METHOD;
         return true;
+      case remoting::mojom::ProtocolErrorCode::kLoginScreenNotSupported:
+        *out = ::remoting::protocol::ErrorCode::LOGIN_SCREEN_NOT_SUPPORTED;
+        return true;
+      case remoting::mojom::ProtocolErrorCode::kSessionPoliciesChanged:
+        *out = ::remoting::protocol::ErrorCode::SESSION_POLICIES_CHANGED;
+        return true;
     }
 
     NOTREACHED();
diff --git a/remoting/host/remoting_me2me_host.cc b/remoting/host/remoting_me2me_host.cc
index 09367b1..84c5a8b 100644
--- a/remoting/host/remoting_me2me_host.cc
+++ b/remoting/host/remoting_me2me_host.cc
@@ -20,6 +20,7 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
+#include "base/logging.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/message_loop/message_pump_type.h"
@@ -96,6 +97,7 @@
 #include "remoting/host/ipc_host_event_logger.h"
 #include "remoting/host/me2me_desktop_environment.h"
 #include "remoting/host/me2me_heartbeat_service_client.h"
+#include "remoting/host/mojom/agent_process_broker.mojom.h"
 #include "remoting/host/mojom/chromoting_host_services.mojom.h"
 #include "remoting/host/mojom/desktop_session.mojom.h"
 #include "remoting/host/mojom/remoting_host.mojom.h"
@@ -128,6 +130,7 @@
 #include <signal.h>
 #include <sys/types.h>
 #include <unistd.h>
+
 #include "base/file_descriptor_posix.h"
 #include "remoting/host/pam_authorization_factory_posix.h"
 #include "remoting/host/posix/signal_handler.h"
@@ -136,6 +139,7 @@
 #if BUILDFLAG(IS_APPLE)
 #include "remoting/host/audio_capturer_mac.h"
 #include "remoting/host/desktop_capturer_checker.h"
+#include "remoting/host/mac/agent_process_broker_client.h"
 #include "remoting/host/mac/permission_utils.h"
 #endif  // BUILDFLAG(IS_APPLE)
 
@@ -162,6 +166,7 @@
 
 #if BUILDFLAG(IS_WIN)
 #include <commctrl.h>
+
 #include "base/win/registry.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/windows_version.h"
@@ -181,9 +186,8 @@
 // The following creates a section that tells Mac OS X that it is OK to let us
 // inject input in the login screen. Just the name of the section is important,
 // not its contents.
-__attribute__((used))
-__attribute__((section("__CGPreLoginApp,__cgpreloginapp")))
-static const char magic_section[] = "";
+__attribute__((used)) __attribute__((section(
+    "__CGPreLoginApp,__cgpreloginapp"))) static const char magic_section[] = "";
 
 #endif  // BUILDFLAG(IS_APPLE)
 
@@ -231,6 +235,7 @@
 const char kHostOfflineReasonPolicyChangeRequiresRestart[] =
     "POLICY_CHANGE_REQUIRES_RESTART";
 const char kHostOfflineReasonZombieStateDetected[] = "ZOMBIE_STATE_DETECTED";
+const char kHostOfflineReasonSuspended[] = "SUSPENDED";
 
 // File to write webrtc trace events to. If not specified, webrtc trace events
 // will not be enabled.
@@ -255,6 +260,9 @@
                     public HeartbeatSender::Delegate,
                     public IPC::Listener,
                     public base::RefCountedThreadSafe<HostProcess>,
+#if BUILDFLAG(IS_MAC)
+                    public mojom::AgentProcess,
+#endif
                     public mojom::RemotingHostControl,
                     public mojom::WorkerProcessControl {
  public:
@@ -282,6 +290,12 @@
   // FtlHostChangeNotificationListener::Listener overrides.
   void OnHostDeleted() override;
 
+#if BUILDFLAG(IS_MAC)
+  // mojom::AgentProcess overrides.
+  void ResumeProcess() override;
+  void SuspendProcess() override;
+#endif
+
  private:
   // See SetState method for a list of allowed state transitions.
   enum HostState {
@@ -301,6 +315,11 @@
 
     // Host has been stopped (host process will end soon).
     HOST_STOPPED,
+
+    // Host has been suspended. Meaning it cannot send heartbeats or connect to
+    // signaling. In this state, it may either resume (transition to
+    // HOST_STARTING) or shut down (transition to HOST_GOING_OFFLINE_TO_STOP).
+    HOST_SUSPENDED,
   };
 
   enum PolicyState {
@@ -371,7 +390,6 @@
   bool OnClientDomainListPolicyUpdate(const base::Value::Dict& policies);
   bool OnHostDomainListPolicyUpdate(const base::Value::Dict& policies);
   bool OnUsernamePolicyUpdate(const base::Value::Dict& policies);
-  bool OnCurtainPolicyUpdate(const base::Value::Dict& policies);
   bool OnPairingPolicyUpdate(const base::Value::Dict& policies);
   bool OnGnubbyAuthPolicyUpdate(const base::Value::Dict& policies);
   bool OnEnableUserInterfacePolicyUpdate(const base::Value::Dict& policies);
@@ -415,8 +433,18 @@
       int peer_pid) override;
 #endif
 
+#if BUILDFLAG(IS_MAC)
+  void ConnectAgentProcessBroker();
+  void OnAgentProcessBrokerDisconnected();
+#endif
+
   std::unique_ptr<ChromotingHostContext> context_;
 
+#if BUILDFLAG(IS_MAC)
+  // Created and used on the network thread.
+  std::unique_ptr<AgentProcessBrokerClient> agent_process_broker_client_;
+#endif
+
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   // Watch for certificate changes and kill the host when changes occur
   std::unique_ptr<CertificateWatcher> cert_watcher_;
@@ -717,10 +745,15 @@
 
 // Allowed state transitions (enforced via DCHECKs in SetState method):
 //   STARTING->STARTED (once we have valid config + policy)
+//   STARTING->SUSPENDED (on Mac where host processes need to be brokered)
 //   STARTING->GOING_OFFLINE_TO_STOP
 //   STARTING->GOING_OFFLINE_TO_RESTART
 //   STARTED->GOING_OFFLINE_TO_STOP
 //   STARTED->GOING_OFFLINE_TO_RESTART
+//   STARTED->SUSPENDED (informed by broker process to give way to process with
+//                       higher priority)
+//   SUSPENDED->STARTING (resumed by broker process)
+//   SUSPENDED->GOING_OFFLINE_TO_STOP
 //   GOING_OFFLINE_TO_RESTART->GOING_OFFLINE_TO_STOP
 //   GOING_OFFLINE_TO_RESTART->STARTING (after OnHostOfflineReasonAck)
 //   GOING_OFFLINE_TO_STOP->STOPPED (after OnHostOfflineReasonAck)
@@ -736,22 +769,30 @@
     case HOST_STARTING:
       DCHECK((target_state == HOST_STARTED) ||
              (target_state == HOST_GOING_OFFLINE_TO_STOP) ||
-             (target_state == HOST_GOING_OFFLINE_TO_RESTART))
+             (target_state == HOST_GOING_OFFLINE_TO_RESTART) ||
+             (target_state == HOST_SUSPENDED))
           << state_ << " -> " << target_state;
       break;
     case HOST_STARTED:
       DCHECK((target_state == HOST_GOING_OFFLINE_TO_STOP) ||
-             (target_state == HOST_GOING_OFFLINE_TO_RESTART))
+             (target_state == HOST_GOING_OFFLINE_TO_RESTART) ||
+             (target_state == HOST_SUSPENDED))
           << state_ << " -> " << target_state;
       break;
     case HOST_GOING_OFFLINE_TO_RESTART:
       DCHECK((target_state == HOST_GOING_OFFLINE_TO_STOP) ||
-             (target_state == HOST_STARTING))
+             (target_state == HOST_STARTING) ||
+             (target_state == HOST_SUSPENDED))
           << state_ << " -> " << target_state;
       break;
     case HOST_GOING_OFFLINE_TO_STOP:
       DCHECK_EQ(target_state, HOST_STOPPED);
       break;
+    case HOST_SUSPENDED:
+      DCHECK((target_state == HOST_GOING_OFFLINE_TO_STOP) ||
+             (target_state == HOST_STARTING))
+          << state_ << " -> " << target_state;
+      break;
     case HOST_STOPPED:  // HOST_STOPPED is a terminal state.
     default:
       NOTREACHED() << state_ << " -> " << target_state;
@@ -1151,6 +1192,30 @@
   ShutdownHost(kHostDeletedExitCode);
 }
 
+#if BUILDFLAG(IS_MAC)
+
+void HostProcess::ResumeProcess() {
+  DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
+  if (state_ == HOST_GOING_OFFLINE_TO_STOP) {
+    return;
+  }
+  HOST_LOG << "Resuming process";
+  SetState(HOST_STARTING);
+  StartHostIfReady();
+}
+
+void HostProcess::SuspendProcess() {
+  DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
+  if (state_ == HOST_SUSPENDED || state_ == HOST_GOING_OFFLINE_TO_STOP) {
+    return;
+  }
+  HOST_LOG << "Suspending process";
+  SetState(HOST_SUSPENDED);
+  GoOffline(kHostOfflineReasonSuspended);
+}
+
+#endif
+
 #if BUILDFLAG(IS_WIN)
 void HostProcess::ApplyHostConfig(base::Value::Dict config) {
   DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
@@ -1212,6 +1277,29 @@
 
 #endif  // BUILDFLAG(IS_WIN)
 
+#if BUILDFLAG(IS_MAC)
+
+void HostProcess::ConnectAgentProcessBroker() {
+  DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
+  agent_process_broker_client_ = std::make_unique<AgentProcessBrokerClient>(
+      base::BindOnce(&HostProcess::OnAgentProcessBrokerDisconnected,
+                     base::Unretained(this)));
+  if (!agent_process_broker_client_->ConnectToServer()) {
+    LOG(ERROR) << "Failed to connect to agent process broker.";
+    ShutdownHost(kInitializationFailed);
+    return;
+  }
+  agent_process_broker_client_->OnAgentProcessLaunched(this);
+}
+
+void HostProcess::OnAgentProcessBrokerDisconnected() {
+  DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
+  HOST_LOG << "Host terminated by agent process broker.";
+  ShutdownHost(kTerminatedByAgentProcessBroker);
+}
+
+#endif  // BUILDFLAG(IS_MAC)
+
 // Applies the host config, returning true if successful.
 bool HostProcess::ApplyConfig(const base::Value::Dict& config) {
   DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
@@ -1325,8 +1413,6 @@
   bool restart_required = false;
   restart_required |= OnClientDomainListPolicyUpdate(policies);
   restart_required |= OnHostDomainListPolicyUpdate(policies);
-  restart_required |= OnCurtainPolicyUpdate(policies);
-  // Note: UsernamePolicyUpdate must run after OnCurtainPolicyUpdate.
   restart_required |= OnUsernamePolicyUpdate(policies);
   restart_required |= OnPairingPolicyUpdate(policies);
   restart_required |= OnGnubbyAuthPolicyUpdate(policies);
@@ -1462,45 +1548,41 @@
     return;
   }
 
+#if BUILDFLAG(IS_WIN)
+  // The RemoteAccessHostMatchUsername policy does not exist on Windows, so this
+  // should be unreached code.
+  NOTREACHED();
+#endif
+
   HOST_LOG << "Policy requires host username match.";
 
 #if BUILDFLAG(IS_APPLE)
-    // On Mac, we run as root at the login screen, so the username won't match.
-    // However, there's no need to enforce the policy at the login screen, as
-    // the client will have to reconnect if a login occurs.
+  // On Mac, we run as root at the login screen, so the username won't match.
+  // However, there's no need to enforce the policy at the login screen, as
+  // the client will have to reconnect if a login occurs.
   if (getuid() == 0) {
     return;
   }
 #endif
 
-    // Curtain-mode on Windows presents the standard OS login prompt to the user
-    // for each connection, removing the need for an explicit user-name matching
-    // check.
-#if BUILDFLAG(IS_WIN) && defined(REMOTING_RDP_SESSION)
-    if (desktop_environment_options_.enable_curtaining()) {
-      return;
+  std::string username = GetUsername();
+  LOG(INFO) << "Current local username is '" << username << "'";
+  std::set<std::string> allowed_emails;
+  for (const std::string& owner_email : host_owner_emails_) {
+    auto [owner_username, _] = *base::SplitStringOnce(owner_email, '@');
+    if (base::EqualsCaseInsensitiveASCII(username, owner_username)) {
+      LOG(INFO) << owner_email << " matches the local username";
+      allowed_emails.emplace(owner_email);
+    } else {
+      LOG(WARNING) << owner_email << " does not match the local username";
     }
-#endif  // BUILDFLAG(IS_WIN) && defined(REMOTING_RDP_SESSION)
+  }
 
-    std::string username = GetUsername();
-    LOG(INFO) << "Current local username is '" << username << "'";
-    std::set<std::string> allowed_emails;
-    for (const std::string& owner_email : host_owner_emails_) {
-      auto [owner_username, _] = *base::SplitStringOnce(owner_email, '@');
-      if (base::EqualsCaseInsensitiveASCII(username, owner_username)) {
-        LOG(INFO) << owner_email << " matches the local username";
-        allowed_emails.emplace(owner_email);
-      } else {
-        LOG(WARNING) << owner_email << " does not match the local username";
-      }
-    }
-
-    host_owner_emails_.swap(allowed_emails);
-    if (host_owner_emails_.empty()) {
-      LOG(ERROR)
-          << "No owner emails are allowed based on match username policy.";
-      ShutdownHost(kUsernameMismatchExitCode);
-    }
+  host_owner_emails_.swap(allowed_emails);
+  if (host_owner_emails_.empty()) {
+    LOG(ERROR) << "No owner emails are allowed based on match username policy.";
+    ShutdownHost(kUsernameMismatchExitCode);
+  }
 }
 
 bool HostProcess::OnUsernamePolicyUpdate(const base::Value::Dict& policies) {
@@ -1520,47 +1602,6 @@
   return false;
 }
 
-bool HostProcess::OnCurtainPolicyUpdate(const base::Value::Dict& policies) {
-  // Returns true if the host has to be restarted after this policy update.
-  DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
-
-  std::optional<bool> curtain_required =
-      policies.FindBool(policy::key::kRemoteAccessHostRequireCurtain);
-  if (!curtain_required.has_value()) {
-    return false;
-  }
-
-  desktop_environment_options_.set_enable_curtaining(*curtain_required);
-
-#if BUILDFLAG(IS_APPLE)
-  if (*curtain_required) {
-    // When curtain mode is in effect on Mac, the host process runs in the
-    // user's switched-out session, but launchd will also run an instance at
-    // the console login screen.  Even if no user is currently logged-on, we
-    // can't support remote-access to the login screen because the current host
-    // process model disconnects the client during login, which would leave
-    // the logged in session un-curtained on the console until they reconnect.
-    //
-    // TODO(jamiewalch): Fix this once we have implemented the multi-process
-    // daemon architecture (crbug.com/134894)
-    if (getuid() == 0) {
-      LOG(ERROR) << "Running the host in the console login session is not yet "
-                    "supported.";
-      ShutdownHost(kLoginScreenNotSupportedExitCode);
-      return false;
-    }
-  }
-#endif
-
-  if (*curtain_required) {
-    HOST_LOG << "Policy requires curtain-mode.";
-  } else {
-    HOST_LOG << "Policy does not require curtain-mode.";
-  }
-
-  return true;
-}
-
 bool HostProcess::OnPairingPolicyUpdate(const base::Value::Dict& policies) {
   DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
 
@@ -1749,6 +1790,15 @@
   DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
   DCHECK(!host_);
 
+#if BUILDFLAG(IS_MAC)
+  if (!agent_process_broker_client_) {
+    HOST_LOG << "Suspending process to wait for broker outcome";
+    SetState(HOST_SUSPENDED);
+    ConnectAgentProcessBroker();
+    return;
+  }
+#endif
+
   // This thread is used as a network thread in WebRTC.
   webrtc::ThreadWrapper::EnsureForCurrentMessageLoop();
 
@@ -1845,7 +1895,7 @@
   daemon_channel_->GetRemoteAssociatedInterface(&remote);
   host_event_logger_ = std::make_unique<IpcHostEventLogger>(
       host_->status_monitor(), std::move(remote));
-#else  // !defined(REMOTING_MULTI_PROCESS)
+#else   // !defined(REMOTING_MULTI_PROCESS)
   host_event_logger_ =
       HostEventLogger::Create(host_->status_monitor(), kApplicationName);
 #endif  // !defined(REMOTING_MULTI_PROCESS)
@@ -1890,6 +1940,7 @@
   *exit_code_out_ = exit_code;
 
   switch (state_) {
+    case HOST_SUSPENDED:
     case HOST_STARTING:
     case HOST_STARTED:
       SetState(HOST_GOING_OFFLINE_TO_STOP);
@@ -1911,7 +1962,8 @@
   DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
   DCHECK(!host_offline_reason.empty());
   DCHECK((state_ == HOST_GOING_OFFLINE_TO_STOP) ||
-         (state_ == HOST_GOING_OFFLINE_TO_RESTART));
+         (state_ == HOST_GOING_OFFLINE_TO_RESTART) ||
+         (state_ == HOST_SUSPENDED));
 
   // Shut down everything except the HostSignalingManager.
   host_.reset();
@@ -1923,9 +1975,16 @@
 
   // Before shutting down HostSignalingManager, send the |host_offline_reason|
   // if possible (i.e. if we have the config).
-  if (host_offline_reason == ExitCodeToString(kHostDeletedExitCode)) {
-    // Host is deleted. There is no need to report the host offline reason back
-    // to directory.
+  if (
+      // Host is deleted. There is no need to report the host offline reason
+      // back to directory.
+      host_offline_reason == ExitCodeToString(kHostDeletedExitCode) ||
+      // kTerminatedByAgentProcessBroker and kHostOfflineReasonSuspended imply
+      // that there is another host process heartbeating. Reporting the offline
+      // reason will make the host appear to be offline.
+      host_offline_reason ==
+          ExitCodeToString(kTerminatedByAgentProcessBroker) ||
+      host_offline_reason == kHostOfflineReasonSuspended) {
     OnHostOfflineReasonAck(true);
     return;
   } else if (!config_.empty()) {
@@ -1970,10 +2029,14 @@
 
     config_watcher_.reset();
 
+#if BUILDFLAG(IS_MAC)
+    agent_process_broker_client_.reset();
+#endif
+
     // Complete the rest of shutdown on the main thread.
     context_->ui_task_runner()->PostTask(
         FROM_HERE, base::BindOnce(&HostProcess::ShutdownOnUiThread, this));
-  } else {
+  } else if (state_ != HOST_SUSPENDED) {
     NOTREACHED();
   }
 }
diff --git a/remoting/host/version.h.in b/remoting/host/version.h.in
index 3ff92d4b..7a9b622f 100644
--- a/remoting/host/version.h.in
+++ b/remoting/host/version.h.in
@@ -9,14 +9,15 @@
 #ifndef REMOTING_VERSION_H_
 #define REMOTING_VERSION_H_
 
-#if defined(OS_APPLE)
+#if BUILDFLAG(IS_APPLE)
 
 #define HOST_BUNDLE_NAME "@MAC_HOST_BUNDLE_NAME@"
 #define NATIVE_MESSAGING_HOST_BUNDLE_NAME \
   "@MAC_NATIVE_MESSAGING_HOST_BUNDLE_NAME@"
 #define REMOTE_ASSISTANCE_HOST_BUNDLE_NAME \
   "@MAC_REMOTE_ASSISTANCE_HOST_BUNDLE_NAME@"
+#define MAC_TEAM_ID "@MAC_TEAM_ID@"
 
-#endif  // defined(OS_APPLE)
+#endif  // BUILDFLAG(IS_APPLE)
 
 #endif  // REMOTING_VERSION_H_
diff --git a/remoting/ios/persistence/remoting_keychain.cc b/remoting/ios/persistence/remoting_keychain.cc
index 0f40373d..bf8e1df 100644
--- a/remoting/ios/persistence/remoting_keychain.cc
+++ b/remoting/ios/persistence/remoting_keychain.cc
@@ -6,6 +6,9 @@
 
 #import <Security/Security.h>
 
+#include <string>
+
+#include "base/apple/foundation_util.h"
 #include "base/apple/scoped_cftyperef.h"
 #include "base/logging.h"
 #include "base/no_destructor.h"
@@ -125,10 +128,9 @@
   if (status != errSecSuccess) {
     LOG(FATAL) << "Failed to query keychain data. Status: " << status;
   }
-  const char* data_pointer =
-      reinterpret_cast<const char*>(CFDataGetBytePtr(cf_result.get()));
-  CFIndex data_size = CFDataGetLength(cf_result.get());
-  return std::string(data_pointer, data_size);
+
+  return std::string(
+      base::as_string_view(base::apple::CFDataToSpan(cf_result.get())));
 }
 
 void RemotingKeychain::RemoveData(Key key, const std::string& account) {
diff --git a/remoting/proto/error_code.proto b/remoting/proto/error_code.proto
index 3175400..378a6d2 100644
--- a/remoting/proto/error_code.proto
+++ b/remoting/proto/error_code.proto
@@ -90,6 +90,13 @@
   // and the client.
   NO_COMMON_AUTH_METHOD = 62;
 
+  // The connection was rejected because the curtain mode policy is set, which
+  // does not support the login screen on some platforms.
+  LOGIN_SCREEN_NOT_SUPPORTED = 63;
+
+  // The connection was disconnected because the session policies have changed.
+  SESSION_POLICIES_CHANGED = 64;
+
   reserved 2, 11, 12, 15, 17, 41;
   reserved 5 to 7, 19 to 25, 28 to 39, 51 to 61;
 
diff --git a/services/device/hid/hid_service_mac.cc b/services/device/hid/hid_service_mac.cc
index 3d7f8ef..07240df1 100644
--- a/services/device/hid/hid_service_mac.cc
+++ b/services/device/hid/hid_service_mac.cc
@@ -20,7 +20,6 @@
 #include "base/functional/bind.h"
 #include "base/location.h"
 #include "base/logging.h"
-#include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/task/sequenced_task_runner.h"
@@ -38,12 +37,12 @@
                            std::vector<uint8_t>* result) {
   base::apple::ScopedCFTypeRef<CFDataRef> ref(base::apple::CFCast<CFDataRef>(
       IORegistryEntryCreateCFProperty(service, key, kCFAllocatorDefault, 0)));
-  if (!ref)
+  if (!ref) {
     return false;
+  }
 
-  base::STLClearObject(result);
-  const uint8_t* bytes = CFDataGetBytePtr(ref.get());
-  result->insert(result->begin(), bytes, bytes + CFDataGetLength(ref.get()));
+  auto data_span = base::apple::CFDataToSpan(ref.get());
+  result->assign(data_span.begin(), data_span.end());
   return true;
 }
 
diff --git a/services/webnn/tflite/graph_builder_tflite.cc b/services/webnn/tflite/graph_builder_tflite.cc
index ba75da9..262ea77a 100644
--- a/services/webnn/tflite/graph_builder_tflite.cc
+++ b/services/webnn/tflite/graph_builder_tflite.cc
@@ -4,6 +4,7 @@
 
 #include "services/webnn/tflite/graph_builder_tflite.h"
 
+#include <cstddef>
 #include <cstdint>
 #include <numeric>
 #include <vector>
@@ -16,6 +17,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/types/expected.h"
 #include "base/types/expected_macros.h"
+#include "base/types/fixed_array.h"
 #include "services/webnn/public/cpp/context_properties.h"
 #include "services/webnn/public/cpp/graph_validation_utils.h"
 #include "services/webnn/public/cpp/webnn_errors.h"
@@ -1260,8 +1262,7 @@
   // Create `tflite::Tensor` for the output operand of explicit padding operator
   // with the dimensions and data type.
   CHECK_EQ(input_tensor_info.dimensions.size(), 4u);
-  std::vector<int32_t> output_shape;
-  output_shape.reserve(padding_rank);
+  base::FixedArray<int32_t> output_shape(padding_rank);
   for (size_t i = 0; i < padding_rank; ++i) {
     auto checked_dimension =
         base::MakeCheckedNum<int32_t>(input_tensor_info.dimensions[i]);
@@ -1277,7 +1278,7 @@
     if (!checked_dimension.IsValid()) {
       return base::unexpected("The input dimension or padding is too large.");
     }
-    output_shape.push_back(checked_dimension.ValueOrDie());
+    output_shape[i] = checked_dimension.ValueOrDie();
   }
 
   const int32_t output_tensor_index =
@@ -1324,10 +1325,9 @@
   // the dimensions and tensor data type.
   const size_t input_rank = input_dimensions.size();
   CHECK_EQ(permutation.size(), input_rank);
-  std::vector<int32_t> output_shape;
-  output_shape.reserve(input_rank);
+  base::FixedArray<int32_t> output_shape(input_rank);
   for (size_t i = 0; i < input_rank; ++i) {
-    output_shape.push_back(input_dimensions[permutation[i]]);
+    output_shape[i] = input_dimensions[permutation[i]];
   }
   const int32_t output_tensor_index =
       SerializeTemporaryTensor(output_shape, input_tensor_type);
@@ -1526,12 +1526,12 @@
 auto GraphBuilderTflite::SerializeConcat(const mojom::Concat& concat)
     -> base::expected<OperatorOffset, std::string> {
   // TODO(crbug.com/369649350): Support float16 without dequantize operator.
-  std::vector<int32_t> operator_inputs_index;
-  operator_inputs_index.reserve(concat.input_operand_ids.size());
-  for (auto input_operand_id : concat.input_operand_ids) {
+  base::FixedArray<int32_t> operator_inputs_index(
+      concat.input_operand_ids.size());
+  for (size_t i = 0; i < concat.input_operand_ids.size(); ++i) {
     ASSIGN_OR_RETURN(const TensorInfo& input_tensor_info,
-                     SerializeInputTensorInfo(input_operand_id));
-    operator_inputs_index.push_back(input_tensor_info.index);
+                     SerializeInputTensorInfo(concat.input_operand_ids[i]));
+    operator_inputs_index[i] = input_tensor_info.index;
   }
   ASSIGN_OR_RETURN(const TensorInfo& output_tensor_info,
                    SerializeOutputTensorInfo(concat.output_operand_id));
@@ -2221,11 +2221,11 @@
                                                hidden_size};
   const int32_t input_size = gru_cell.input_dimensions[1];
 
-  std::vector<::tflite::BuiltinOperator> activation_operator_codes;
-  activation_operator_codes.reserve(gru_cell.activations.size());
-  for (mojom::RecurrentNetworkActivation activation : gru_cell.activations) {
-    activation_operator_codes.push_back(
-        GetRecurrentNetworkActivation(activation));
+  base::FixedArray<::tflite::BuiltinOperator> activation_operator_codes(
+      gru_cell.activations.size());
+  for (size_t i = 0; i < gru_cell.activations.size(); ++i) {
+    activation_operator_codes[i] =
+        GetRecurrentNetworkActivation(gru_cell.activations[i]);
   }
 
   ::tflite::BuiltinOperator activation_code;
@@ -2522,11 +2522,11 @@
                                                hidden_size};
   const int32_t input_size = lstm_cell.input_dimensions[1];
 
-  std::vector<::tflite::BuiltinOperator> activation_operator_codes;
-  activation_operator_codes.reserve(lstm_cell.activations.size());
-  for (mojom::RecurrentNetworkActivation activation : lstm_cell.activations) {
-    activation_operator_codes.push_back(
-        GetRecurrentNetworkActivation(activation));
+  base::FixedArray<::tflite::BuiltinOperator> activation_operator_codes(
+      lstm_cell.activations.size());
+  for (size_t i = 0; i < lstm_cell.activations.size(); ++i) {
+    activation_operator_codes[i] =
+        GetRecurrentNetworkActivation(lstm_cell.activations[i]);
   }
 
   CHECK(lstm_cell.layout == mojom::LstmWeightLayout::kIofg ||
@@ -2728,11 +2728,10 @@
                               slice_starts, slice_sizes));
   operators_.emplace_back(operator_offset);
 
-  std::vector<int32_t> squeeze_output_shape;
-  squeeze_output_shape.reserve(slice_sizes.size());
+  base::FixedArray<int32_t> squeeze_output_shape(slice_sizes.size());
   for (size_t i = 0; i < slice_sizes.size(); ++i) {
     if (base::checked_cast<size_t>(squeeze_axis) != i) {
-      squeeze_output_shape.push_back(slice_sizes[i]);
+      squeeze_output_shape[i] = slice_sizes[i];
     }
   }
   const int32_t output_tensor_index =
@@ -2807,10 +2806,9 @@
                          initial_hidden_cell_state_shape));
   }
 
-  std::vector<int32_t> cell_weight_tensor_indices;
-  cell_weight_tensor_indices.reserve(num_directions);
-  std::vector<int32_t> cell_recurrent_weight_tensor_indices;
-  cell_recurrent_weight_tensor_indices.reserve(num_directions);
+  base::FixedArray<int32_t> cell_weight_tensor_indices(num_directions);
+  base::FixedArray<int32_t> cell_recurrent_weight_tensor_indices(
+      num_directions);
   std::vector<int32_t> cell_bias_tensor_indices;
   cell_bias_tensor_indices.reserve(num_directions);
   std::vector<int32_t> cell_recurrent_bias_tensor_indices;
@@ -2857,7 +2855,7 @@
                          /*slice_sizes=*/
                          std::array<int32_t, 3>({1, slice_height, input_size}),
                          /*squeeze_axis=*/0));
-    cell_weight_tensor_indices.push_back(cell_weight_tensor_index);
+    cell_weight_tensor_indices[slot] = cell_weight_tensor_index;
 
     ASSIGN_OR_RETURN(const int32_t cell_recurrent_weight_tensor_index,
                      SerializeSubGraphSliceSqueeze(
@@ -2866,8 +2864,8 @@
                          /*slice_sizes=*/
                          std::array<int32_t, 3>({1, slice_height, hidden_size}),
                          /*squeeze_axis=*/0));
-    cell_recurrent_weight_tensor_indices.push_back(
-        cell_recurrent_weight_tensor_index);
+    cell_recurrent_weight_tensor_indices[slot] =
+        cell_recurrent_weight_tensor_index;
 
     if (recurrent_network.bias_operand_id) {
       ASSIGN_OR_RETURN(const int32_t cell_bias_tensor_index,
@@ -2910,8 +2908,7 @@
 
   std::optional<int32_t> sequence_tensor_index;
   for (int32_t step = 0; step < recurrent_steps; ++step) {
-    std::vector<int32_t> current_hidden_tensor_indices;
-    current_hidden_tensor_indices.reserve(num_directions);
+    base::FixedArray<int32_t> current_hidden_tensor_indices(num_directions);
     std::vector<int32_t> lstm_current_cell_tensor_indices;
     if constexpr (std::is_same<RecurrentNetworkType, mojom::Lstm>::value) {
       lstm_current_cell_tensor_indices.reserve(num_directions);
@@ -2925,7 +2922,7 @@
               /*slice_sizes=*/
               std::array<int32_t, 3>({1, batch_size, hidden_size}),
               /*squeeze_axis=*/0));
-      current_hidden_tensor_indices.push_back(cell_hidden_tensor_index);
+      current_hidden_tensor_indices[slot] = cell_hidden_tensor_index;
 
       if constexpr (std::is_same<RecurrentNetworkType, mojom::Lstm>::value) {
         ASSIGN_OR_RETURN(
@@ -3063,12 +3060,13 @@
     }
   }
 
-  std::vector<int32_t> output_tensor_indices;
-  output_tensor_indices.reserve(recurrent_network.output_operand_ids.size());
-  for (auto operand_id : recurrent_network.output_operand_ids) {
-    ASSIGN_OR_RETURN(const TensorInfo& output_tensor_info,
-                     SerializeOutputTensorInfo(operand_id));
-    output_tensor_indices.push_back(output_tensor_info.index);
+  base::FixedArray<int32_t> output_tensor_indices(
+      recurrent_network.output_operand_ids.size());
+  for (size_t i = 0; i < recurrent_network.output_operand_ids.size(); ++i) {
+    ASSIGN_OR_RETURN(
+        const TensorInfo& output_tensor_info,
+        SerializeOutputTensorInfo(recurrent_network.output_operand_ids[i]));
+    output_tensor_indices[i] = output_tensor_info.index;
   }
   if (recurrent_network.return_sequence) {
     int32_t output_sequence_tensor_index;
@@ -3436,12 +3434,12 @@
         SerializeInputTensorInfo(*lstm_cell.peephole_weight_operand_id));
     peephole_weight_tensor_index = peephole_weight_tensor_info.index;
   }
-  std::vector<int32_t> output_tensor_indices;
-  output_tensor_indices.reserve(lstm_cell.output_operand_ids.size());
-  for (auto operand_id : lstm_cell.output_operand_ids) {
-    ASSIGN_OR_RETURN(const TensorInfo& output_tensor_info,
-                     SerializeOutputTensorInfo(operand_id));
-    output_tensor_indices.push_back(output_tensor_info.index);
+  base::FixedArray<int32_t> output_tensor_indices(lstm_cell.output_operand_ids.size());
+  for (size_t i = 0; i < lstm_cell.output_operand_ids.size(); ++i) {
+    ASSIGN_OR_RETURN(
+        const TensorInfo& output_tensor_info,
+        SerializeOutputTensorInfo(lstm_cell.output_operand_ids[i]));
+    output_tensor_indices[i] = output_tensor_info.index;
   }
 
   CHECK_EQ(input_tensor_info.dimensions.size(), 2u);
@@ -3995,18 +3993,17 @@
 
   // The number of starts and sizes are the same as input rank that is verified
   // in ValidateSliceAndInferOutput() function.
-  std::vector<int32_t> slice_starts;
-  slice_starts.reserve(slice.starts_and_sizes.size());
-  std::vector<int32_t> slice_sizes;
-  slice_sizes.reserve(slice.starts_and_sizes.size());
-  for (auto& start_and_size : slice.starts_and_sizes) {
+  base::FixedArray<int32_t> slice_starts(slice.starts_and_sizes.size());
+  base::FixedArray<int32_t> slice_sizes(slice.starts_and_sizes.size());
+  for (size_t i = 0; i < slice.starts_and_sizes.size(); ++i) {
+    const auto& start_and_size = slice.starts_and_sizes[i];
     auto checked_start = base::MakeCheckedNum<int32_t>(start_and_size->start);
     auto checked_size = base::MakeCheckedNum<int32_t>(start_and_size->size);
     if (!checked_start.IsValid() || !checked_size.IsValid()) {
       return base::unexpected("The start or size of slice is too large.");
     }
-    slice_starts.push_back(checked_start.ValueOrDie());
-    slice_sizes.push_back(checked_size.ValueOrDie());
+    slice_starts[i] = checked_start.ValueOrDie();
+    slice_sizes[i] = checked_size.ValueOrDie();
   }
 
   ASSIGN_OR_RETURN(const TensorInfo& input_tensor_info,
@@ -4152,17 +4149,14 @@
   // Serialize the split sizes tensor that specifies the sizes of each output
   // tensor along the axis.
   const size_t outputs_size = split.output_operand_ids.size();
-  std::vector<int32_t> split_sizes;
-  split_sizes.reserve(outputs_size);
-  std::vector<int32_t> op_outputs;
-  op_outputs.reserve(outputs_size);
-  for (uint64_t output_id : split.output_operand_ids) {
+  base::FixedArray<int32_t> split_sizes(outputs_size);
+  base::FixedArray<int32_t> op_outputs(outputs_size);
+  for (size_t i = 0; i < outputs_size; ++i) {
     ASSIGN_OR_RETURN(const TensorInfo& output_tensor_info,
-                     SerializeOutputTensorInfo(output_id));
+                     SerializeOutputTensorInfo(split.output_operand_ids[i]));
     CHECK_LT(split.axis, output_tensor_info.dimensions.size());
-    split_sizes.push_back(output_tensor_info.dimensions[split.axis]);
-
-    op_outputs.push_back(output_tensor_info.index);
+    split_sizes[i] = output_tensor_info.dimensions[split.axis];
+    op_outputs[i] = output_tensor_info.index;
   }
   const auto checked_split_size =
       base::MakeCheckedNum<int32_t>(split_sizes.size());
diff --git a/storage/browser/file_system/file_system_request_info.cc b/storage/browser/file_system/file_system_request_info.cc
index 3638548d..2fd2451 100644
--- a/storage/browser/file_system/file_system_request_info.cc
+++ b/storage/browser/file_system/file_system_request_info.cc
@@ -3,16 +3,17 @@
 // found in the LICENSE file.
 
 #include "storage/browser/file_system/file_system_request_info.h"
+
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "url/gurl.h"
 
 namespace storage {
 
 FileSystemRequestInfo::FileSystemRequestInfo(
-    const GURL url,
-    const std::string storage_domain,
+    const GURL& url,
+    const std::string& storage_domain,
     int content_id,
-    const blink::StorageKey storage_key)
+    const blink::StorageKey& storage_key)
     : url(url),
       storage_domain(storage_domain),
       content_id(content_id),
diff --git a/storage/browser/file_system/file_system_request_info.h b/storage/browser/file_system/file_system_request_info.h
index 23161c56..23aba1c 100644
--- a/storage/browser/file_system/file_system_request_info.h
+++ b/storage/browser/file_system/file_system_request_info.h
@@ -28,10 +28,10 @@
   // The original request blink::StorageKey (always set).
   blink::StorageKey storage_key;
 
-  FileSystemRequestInfo(const GURL url,
-                        const std::string storage_domain,
+  FileSystemRequestInfo(const GURL& url,
+                        const std::string& storage_domain,
                         int content_id,
-                        const blink::StorageKey storage_key);
+                        const blink::StorageKey& storage_key);
 };
 
 }  // namespace storage
diff --git a/storage/common/file_system/file_system_util.cc b/storage/common/file_system/file_system_util.cc
index 2b2ed960..2fa1ebe 100644
--- a/storage/common/file_system/file_system_util.cc
+++ b/storage/common/file_system/file_system_util.cc
@@ -305,7 +305,7 @@
 #endif
 }
 
-bool GetFileSystemPublicType(const std::string type_string,
+bool GetFileSystemPublicType(const std::string& type_string,
                              blink::WebFileSystemType* type) {
   DCHECK(type);
   if (type_string == "Temporary") {
diff --git a/storage/common/file_system/file_system_util.h b/storage/common/file_system/file_system_util.h
index ebebd0e6..6bf016e 100644
--- a/storage/common/file_system/file_system_util.h
+++ b/storage/common/file_system/file_system_util.h
@@ -107,7 +107,7 @@
 // Sets type to FileSystemType enum that corresponds to the string name.
 // Returns false if the |type_string| is invalid.
 COMPONENT_EXPORT(STORAGE_COMMON)
-bool GetFileSystemPublicType(std::string type_string,
+bool GetFileSystemPublicType(const std::string& type_string,
                              blink::WebFileSystemType* type);
 
 // Encodes |file_path| to a string.
diff --git a/testing/buildbot/check.py b/testing/buildbot/check.py
index 72a3c73c..5d16fbe4 100755
--- a/testing/buildbot/check.py
+++ b/testing/buildbot/check.py
@@ -35,6 +35,7 @@
 
     # These targets are used by builders setting their tests in starlark
     'android_lint',
+    'cast_android_cma_backend_unittests',
     'cast_audio_backend_unittests',
     'cast_base_junit_tests',
     'cast_base_unittests',
diff --git a/testing/buildbot/chromium.build.fyi.json b/testing/buildbot/chromium.build.fyi.json
index 0adb4f5..6e04753 100644
--- a/testing/buildbot/chromium.build.fyi.json
+++ b/testing/buildbot/chromium.build.fyi.json
@@ -1354,6 +1354,7 @@
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
+            "gpu": null,
             "os": "Mac-14"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -1383,6 +1384,7 @@
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
+            "gpu": null,
             "os": "Mac-14"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
diff --git a/testing/buildbot/chromium.cft.json b/testing/buildbot/chromium.cft.json
index 68c287b..9107e10 100644
--- a/testing/buildbot/chromium.cft.json
+++ b/testing/buildbot/chromium.cft.json
@@ -3302,6 +3302,7 @@
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
+            "gpu": null,
             "os": "Mac-14"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index d27027d..2b48692 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -1356,6 +1356,7 @@
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
+            "gpu": null,
             "os": "Mac-11|Mac-10.16"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -1385,6 +1386,7 @@
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
+            "gpu": null,
             "os": "Mac-11|Mac-10.16"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -3053,6 +3055,7 @@
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
+            "gpu": null,
             "os": "Mac-12"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -3083,6 +3086,7 @@
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
+            "gpu": null,
             "os": "Mac-12"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -4752,6 +4756,7 @@
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
+            "gpu": null,
             "os": "Mac-13"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -4781,6 +4786,7 @@
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
+            "gpu": null,
             "os": "Mac-13"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -28773,6 +28779,7 @@
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
+            "gpu": null,
             "os": "Mac-14"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -28802,6 +28809,7 @@
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
+            "gpu": null,
             "os": "Mac-14"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
diff --git a/testing/buildbot/generate_buildbot_json.py b/testing/buildbot/generate_buildbot_json.py
index feeb0d17..62a5d3d 100755
--- a/testing/buildbot/generate_buildbot_json.py
+++ b/testing/buildbot/generate_buildbot_json.py
@@ -545,33 +545,15 @@
     if substituted_array != original_args:
       test_config['args'] = self.maybe_fixup_args_array(substituted_array)
 
-  def dictionary_merge(self, a, b, path=None):
-    """http://stackoverflow.com/questions/7204805/
-        python-dictionaries-of-dictionaries-merge
-    merges b into a
-    """
-    if path is None:
-      path = []
-    for key in b:
-      if key not in a:
-        if b[key] is not None:
-          a[key] = b[key]
-        continue
-
-      if isinstance(a[key], dict) and isinstance(b[key], dict):
-        self.dictionary_merge(a[key], b[key], path + [str(key)])
-      elif a[key] == b[key]:
-        pass  # same leaf value
-      elif isinstance(a[key], list) and isinstance(b[key], list):
-        a[key] = a[key] + b[key]
-        if key.endswith('args'):
-          a[key] = self.maybe_fixup_args_array(a[key])
-      elif b[key] is None:
-        del a[key]
-      else:
-        a[key] = b[key]
-
-    return a
+  @staticmethod
+  def merge_swarming(swarming1, swarming2):
+    swarming2 = dict(swarming2)
+    if 'dimensions' in swarming2:
+      swarming1.setdefault('dimensions', {}).update(swarming2.pop('dimensions'))
+    if 'named_caches' in swarming2:
+      named_caches = swarming1.setdefault('named_caches', [])
+      named_caches.extend(swarming2.pop('named_caches'))
+    swarming1.update(swarming2)
 
   def clean_swarming_dictionary(self, swarming_dict):
     # Clean out redundant entries from a test's "swarming" dictionary.
@@ -593,7 +575,7 @@
     ):
       swarming = test.pop(key, None)
       if swarming and fn(builder):
-        self.dictionary_merge(test['swarming'], swarming)
+        self.merge_swarming(test['swarming'], swarming)
 
     for key, fn in (
         ('desktop_args', lambda cfg: not self.is_android(cfg)),
@@ -659,7 +641,7 @@
       test = self.apply_mixins(test, variant_mixins, mixins_to_ignore, builder)
 
     # Add any swarming or args from the builder
-    self.dictionary_merge(test['swarming'], builder.get('swarming', {}))
+    self.merge_swarming(test['swarming'], builder.get('swarming', {}))
     if supports_args:
       test.setdefault('args', []).extend(builder.get('args', []))
 
@@ -679,7 +661,7 @@
     # test's specification.
     modifications = self.get_test_modifications(test, builder_name)
     if modifications:
-      test = self.dictionary_merge(test, modifications)
+      test = self.apply_mixin(modifications, test, builder)
 
     # Clean up the swarming entry or remove it if it's unnecessary
     if (swarming_dict := test.get('swarming')) is not None:
@@ -1407,20 +1389,8 @@
       new_test['description'] = '\n'.join(description)
 
     if 'swarming' in mixin:
-      swarming_mixin = mixin['swarming']
-      new_test.setdefault('swarming', {})
-      if 'dimensions' in swarming_mixin:
-        new_test['swarming'].setdefault('dimensions', {}).update(
-            swarming_mixin.pop('dimensions'))
-      if 'named_caches' in swarming_mixin:
-        new_test['swarming'].setdefault('named_caches', []).extend(
-            swarming_mixin['named_caches'])
-        del swarming_mixin['named_caches']
-      # python dict update doesn't do recursion at all. Just hard code the
-      # nested update we need (mixin['swarming'] shouldn't clobber
-      # test['swarming'], but should update it).
-      new_test['swarming'].update(swarming_mixin)
-      del mixin['swarming']
+      self.merge_swarming(new_test.setdefault('swarming', {}),
+                          mixin.pop('swarming'))
 
     for a in ('args', 'precommit_args', 'non_precommit_args'):
       if (value := mixin.pop(a, None)) is None:
diff --git a/testing/buildbot/unittest_expectations/test_test_with_explicit_none/chromium.test.json b/testing/buildbot/unittest_expectations/test_test_with_explicit_none/chromium.test.json
index fbb22c27..5df89cb 100644
--- a/testing/buildbot/unittest_expectations/test_test_with_explicit_none/chromium.test.json
+++ b/testing/buildbot/unittest_expectations/test_test_with_explicit_none/chromium.test.json
@@ -10,6 +10,7 @@
         "name": "foo_test",
         "swarming": {
           "dimensions": {
+            "integrity": null,
             "kvm": "1",
             "os": "Linux"
           },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index b6453a6e6..a21b80c 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1,20 +1,4 @@
 {
-    "AAudioInputStudy": [
-        {
-            "platforms": [
-                "android",
-                "android_webview"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "UseAAudioInput"
-                    ]
-                }
-            ]
-        }
-    ],
     "ALPSNewCodepoint": [
         {
             "platforms": [
@@ -1957,21 +1941,6 @@
             ]
         }
     ],
-    "AutofillEnableVirtualCards": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "AutofillEnableVirtualCards"
-                    ]
-                }
-            ]
-        }
-    ],
     "AutofillFixCachingOnJavaScriptChanges": [
         {
             "platforms": [
@@ -8802,28 +8771,6 @@
             ]
         }
     ],
-    "DropUnrecognizedTemplateUrlParameters": [
-        {
-            "platforms": [
-                "android_webview",
-                "android",
-                "chromeos",
-                "chromeos_lacros",
-                "ios",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "DropUnrecognizedTemplateUrlParameters"
-                    ]
-                }
-            ]
-        }
-    ],
     "DynamicScrollCullRectExpansion": [
         {
             "platforms": [
@@ -19696,6 +19643,21 @@
             ]
         }
     ],
+    "RetainOmniboxOnFocus": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "RetainOmniboxOnFocus"
+                    ]
+                }
+            ]
+        }
+    ],
     "RetryGetVideoCaptureDeviceInfos": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index cc44090..80e5e61 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit cc44090d3483efdbae0dacf9c3fdb6c5d5a950fa
+Subproject commit 80e5e611e4b28d33c0c0359e7654a02703aead46
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index 35da956..6d8225e 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -4483,6 +4483,7 @@
   kHTMLUnsafeMethods = 5109,
   kV8GPUSupportedLimits_MaxInterStageShaderComponents_AttributeGetter = 5110,
   kMaxInterStageShaderComponentsRequiredLimit = 5111,
+  kShowPickerSelect = 5112,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots. Also don't add extra
diff --git a/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom
index af4a2a2..ce538e17 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom
@@ -182,6 +182,7 @@
   kAborting = 124,
   kEditContext = 125,
   kPaintOrder = 126,
+  kShowPickerSelect = 127,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/renderer/core/animation/interpolable_length.cc b/third_party/blink/renderer/core/animation/interpolable_length.cc
index cb2de1d..ca7d699 100644
--- a/third_party/blink/renderer/core/animation/interpolable_length.cc
+++ b/third_party/blink/renderer/core/animation/interpolable_length.cc
@@ -93,7 +93,7 @@
       return CSSValueID::kMaxContent;
     case Length::Type::kFitContent:
       return CSSValueID::kFitContent;
-    case Length::Type::kFillAvailable:
+    case Length::Type::kStretch:
       return CSSValueID::kWebkitFillAvailable;
     case Length::Type::kContent:  // only valid for flex-basis.
       return CSSValueID::kContent;
@@ -116,7 +116,7 @@
     case CSSValueID::kWebkitFitContent:
       return Length::Type::kFitContent;
     case CSSValueID::kWebkitFillAvailable:
-      return Length::Type::kFillAvailable;
+      return Length::Type::kStretch;
     case CSSValueID::kContent:  // only valid for flex-basis.
       return Length::Type::kContent;
     default:
diff --git a/third_party/blink/renderer/core/css/css_identifier_value.cc b/third_party/blink/renderer/core/css/css_identifier_value.cc
index 9423c91..36740039 100644
--- a/third_party/blink/renderer/core/css/css_identifier_value.cc
+++ b/third_party/blink/renderer/core/css/css_identifier_value.cc
@@ -49,7 +49,7 @@
     case Length::kMaxContent:
       value_id_ = CSSValueID::kMaxContent;
       break;
-    case Length::kFillAvailable:
+    case Length::kStretch:
       value_id_ = CSSValueID::kWebkitFillAvailable;
       break;
     case Length::kFitContent:
diff --git a/third_party/blink/renderer/core/css/css_value.cc b/third_party/blink/renderer/core/css/css_value.cc
index ef2fbb6..b075a332 100644
--- a/third_party/blink/renderer/core/css/css_value.cc
+++ b/third_party/blink/renderer/core/css/css_value.cc
@@ -108,7 +108,7 @@
     case Length::kAuto:
     case Length::kMinContent:
     case Length::kMaxContent:
-    case Length::kFillAvailable:
+    case Length::kStretch:
     case Length::kFitContent:
     case Length::kContent:
     case Length::kExtendToZoom:
diff --git a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
index 5d4ac28c..580dea72 100644
--- a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
@@ -1851,7 +1851,7 @@
     case CSSValueID::kWebkitMaxContent:
       return Length::MaxContent();
     case CSSValueID::kWebkitFillAvailable:
-      return Length::FillAvailable();
+      return Length::Stretch();
     case CSSValueID::kWebkitFitContent:
     case CSSValueID::kFitContent:
       return Length::FitContent();
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index 471271d..80eb527 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -1056,9 +1056,7 @@
   AtomicString key;
   if (text.length() >= 1024) {
     size_t digest = FastHash(text.RawByteSpan());
-    LChar digest_as_char[sizeof(digest)];
-    memcpy(digest_as_char, &digest, sizeof(digest));
-    key = AtomicString(digest_as_char, sizeof(digest));
+    key = AtomicString(base::byte_span_from_ref(digest));
   } else {
     key = AtomicString(text);
   }
diff --git a/third_party/blink/renderer/core/dom/abort_signal.cc b/third_party/blink/renderer/core/dom/abort_signal.cc
index be216cd..04db9ab 100644
--- a/third_party/blink/renderer/core/dom/abort_signal.cc
+++ b/third_party/blink/renderer/core/dom/abort_signal.cc
@@ -21,8 +21,8 @@
 #include "third_party/blink/renderer/core/event_type_names.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/bindings/exception_code.h"
-#include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_linked_hash_set.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -167,11 +167,11 @@
   return abort_reason_;
 }
 
-void AbortSignal::throwIfAborted(ScriptState* script_state,
-                                 ExceptionState& exception_state) const {
+void AbortSignal::throwIfAborted() const {
   if (!aborted())
     return;
-  exception_state.RethrowV8Exception(reason(script_state).V8Value());
+  V8ThrowException::ThrowException(execution_context_->GetIsolate(),
+                                   abort_reason_.V8Value());
 }
 
 const AtomicString& AbortSignal::InterfaceName() const {
diff --git a/third_party/blink/renderer/core/dom/abort_signal.h b/third_party/blink/renderer/core/dom/abort_signal.h
index 48effbf..d93f460 100644
--- a/third_party/blink/renderer/core/dom/abort_signal.h
+++ b/third_party/blink/renderer/core/dom/abort_signal.h
@@ -20,7 +20,6 @@
 class AbortController;
 class AbortSignalCompositionManager;
 class AbortSignalRegistry;
-class ExceptionState;
 class ExecutionContext;
 class ScriptState;
 
@@ -97,7 +96,7 @@
   static AbortSignal* timeout(ScriptState*, uint64_t milliseconds);
   ScriptValue reason(ScriptState*) const;
   bool aborted() const { return !abort_reason_.IsEmpty(); }
-  void throwIfAborted(ScriptState*, ExceptionState&) const;
+  void throwIfAborted() const;
   DEFINE_ATTRIBUTE_EVENT_LISTENER(abort, kAbort)
 
   const AtomicString& InterfaceName() const override;
diff --git a/third_party/blink/renderer/core/dom/abort_signal.idl b/third_party/blink/renderer/core/dom/abort_signal.idl
index f807b7c..d0d3318 100644
--- a/third_party/blink/renderer/core/dom/abort_signal.idl
+++ b/third_party/blink/renderer/core/dom/abort_signal.idl
@@ -25,7 +25,7 @@
 
     readonly attribute boolean aborted;
     [CallWith=ScriptState] readonly attribute any reason;
-    [CallWith=ScriptState, MeasureAs=AbortSignalThrowIfAborted, RaisesException] void throwIfAborted();
+    [MeasureAs=AbortSignalThrowIfAborted] void throwIfAborted();
 
     attribute EventHandler onabort;
 };
diff --git a/third_party/blink/renderer/core/frame/history.cc b/third_party/blink/renderer/core/frame/history.cc
index d5e533f..528be664 100644
--- a/third_party/blink/renderer/core/frame/history.cc
+++ b/third_party/blink/renderer/core/frame/history.cc
@@ -31,6 +31,7 @@
 #include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-shared.h"
 #include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_scroll_restoration.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/frame/history_util.h"
@@ -120,9 +121,8 @@
   return nullptr;
 }
 
-void History::setScrollRestoration(const String& value,
+void History::setScrollRestoration(const V8ScrollRestoration& value,
                                    ExceptionState& exception_state) {
-  DCHECK(value == "manual" || value == "auto");
   HistoryItem* item = GetHistoryItem();
   if (!item) {
     exception_state.ThrowSecurityError(
@@ -132,8 +132,9 @@
   }
 
   mojom::blink::ScrollRestorationType scroll_restoration =
-      value == "manual" ? mojom::blink::ScrollRestorationType::kManual
-                        : mojom::blink::ScrollRestorationType::kAuto;
+      value.AsEnum() == V8ScrollRestoration::Enum::kManual
+          ? mojom::blink::ScrollRestorationType::kManual
+          : mojom::blink::ScrollRestorationType::kAuto;
   if (scroll_restoration == ScrollRestorationInternal())
     return;
 
@@ -141,17 +142,19 @@
   DomWindow()->GetFrame()->Client()->DidUpdateCurrentHistoryItem();
 }
 
-String History::scrollRestoration(ExceptionState& exception_state) {
+V8ScrollRestoration History::scrollRestoration(
+    ExceptionState& exception_state) {
   if (!DomWindow()) {
     exception_state.ThrowSecurityError(
         "May not use a History object associated with a Document that is not "
         "fully active");
-    return "auto";
+    return V8ScrollRestoration(V8ScrollRestoration::Enum::kAuto);
   }
-  return ScrollRestorationInternal() ==
-                 mojom::blink::ScrollRestorationType::kManual
-             ? "manual"
-             : "auto";
+  return V8ScrollRestoration(
+      ScrollRestorationInternal() ==
+              mojom::blink::ScrollRestorationType::kManual
+          ? V8ScrollRestoration::Enum::kManual
+          : V8ScrollRestoration::Enum::kAuto);
 }
 
 mojom::blink::ScrollRestorationType History::ScrollRestorationInternal() const {
diff --git a/third_party/blink/renderer/core/frame/history.h b/third_party/blink/renderer/core/frame/history.h
index ef6aa74..262e327 100644
--- a/third_party/blink/renderer/core/frame/history.h
+++ b/third_party/blink/renderer/core/frame/history.h
@@ -44,6 +44,7 @@
 class ExceptionState;
 class HistoryItem;
 class ScriptState;
+class V8ScrollRestoration;
 
 // This class corresponds to the History interface.
 class CORE_EXPORT History final : public ScriptWrappable,
@@ -72,8 +73,8 @@
                     const String& url,
                     ExceptionState& exception_state);
 
-  void setScrollRestoration(const String& value, ExceptionState&);
-  String scrollRestoration(ExceptionState&);
+  void setScrollRestoration(const V8ScrollRestoration& value, ExceptionState&);
+  V8ScrollRestoration scrollRestoration(ExceptionState&);
 
   bool IsSameAsCurrentState(SerializedScriptValue*) const;
 
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.cc b/third_party/blink/renderer/core/html/forms/html_select_element.cc
index 590642f..67d5dd85 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.cc
@@ -33,6 +33,7 @@
 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/public/strings/grit/blink_strings.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_mutation_observer_init.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_union_htmlelement_long.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_union_htmloptgroupelement_htmloptionelement.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
@@ -45,6 +46,8 @@
 #include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
 #include "third_party/blink/renderer/core/dom/events/simulated_click_options.h"
 #include "third_party/blink/renderer/core/dom/focus_params.h"
+#include "third_party/blink/renderer/core/dom/mutation_observer.h"
+#include "third_party/blink/renderer/core/dom/mutation_record.h"
 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
 #include "third_party/blink/renderer/core/dom/node_lists_node_data.h"
 #include "third_party/blink/renderer/core/dom/node_traversal.h"
@@ -60,8 +63,13 @@
 #include "third_party/blink/renderer/core/html/forms/html_options_collection.h"
 #include "third_party/blink/renderer/core/html/forms/html_selected_option_element.h"
 #include "third_party/blink/renderer/core/html/forms/select_type.h"
+#include "third_party/blink/renderer/core/html/html_div_element.h"
 #include "third_party/blink/renderer/core/html/html_hr_element.h"
+#include "third_party/blink/renderer/core/html/html_no_script_element.h"
+#include "third_party/blink/renderer/core/html/html_script_element.h"
 #include "third_party/blink/renderer/core/html/html_slot_element.h"
+#include "third_party/blink/renderer/core/html/html_span_element.h"
+#include "third_party/blink/renderer/core/html/html_template_element.h"
 #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
 #include "third_party/blink/renderer/core/html_names.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
@@ -90,6 +98,65 @@
 // absent.
 const int kDefaultListBoxSize = 4;
 
+class SelectDescendantsObserver : public MutationObserver::Delegate {
+ public:
+  explicit SelectDescendantsObserver(HTMLSelectElement& select)
+      : select_(select), observer_(MutationObserver::Create(this)) {
+    MutationObserverInit* init = MutationObserverInit::Create();
+    init->setChildList(true);
+    init->setSubtree(true);
+    observer_->observe(select_, init, ASSERT_NO_EXCEPTION);
+  }
+
+  ExecutionContext* GetExecutionContext() const override {
+    return select_->GetExecutionContext();
+  }
+
+  void Deliver(const MutationRecordVector& records,
+               MutationObserver&) override {
+    for (const auto& record : records) {
+      if (record->type() == "childList") {
+        for (unsigned i = 0; i < record->addedNodes()->length(); ++i) {
+          if (auto* html_element =
+                  DynamicTo<HTMLElement>(record->addedNodes()->item(i))) {
+            if (!IsDescendantAllowed(html_element)) {
+              // TODO(ansollan): Report an Issue to the DevTools' Issue Panel as
+              // well.
+              html_element->AddConsoleMessage(
+                  mojom::blink::ConsoleMessageSource::kRecommendation,
+                  mojom::blink::ConsoleMessageLevel::kWarning,
+                  "A descendant of a <select> does not follow the content "
+                  "model.");
+              break;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  void Trace(Visitor* visitor) const override {
+    visitor->Trace(select_);
+    visitor->Trace(observer_);
+    MutationObserver::Delegate::Trace(visitor);
+  }
+
+ private:
+  bool IsDescendantAllowed(HTMLElement* element) {
+    // TODO(ansollan): This should be looking at the tree structure to decide if
+    // this is a valid descendant.
+    return IsA<HTMLOptionElement>(element) ||
+           IsA<HTMLOptGroupElement>(element) || IsA<HTMLHRElement>(element) ||
+           IsA<HTMLScriptElement>(element) ||
+           IsA<HTMLTemplateElement>(element) ||
+           IsA<HTMLNoScriptElement>(element) ||
+           IsA<HTMLButtonElement>(element) || IsA<HTMLDivElement>(element) ||
+           IsA<HTMLSpanElement>(element);
+  }
+  Member<HTMLSelectElement> select_;
+  Member<MutationObserver> observer_;
+};
+
 HTMLSelectElement::HTMLSelectElement(Document& document)
     : HTMLFormControlElementWithState(html_names::kSelectTag, document),
       type_ahead_(this),
@@ -102,6 +169,10 @@
   select_type_ = SelectType::Create(*this);
   SetHasCustomStyleCallbacks();
   EnsureUserAgentShadowRoot(SlotAssignmentMode::kManual);
+  if (RuntimeEnabledFeatures::CustomizableSelectEnabled()) {
+    descendants_observer_ =
+        MakeGarbageCollected<SelectDescendantsObserver>(*this);
+  }
 }
 
 HTMLSelectElement::~HTMLSelectElement() = default;
@@ -1357,6 +1428,7 @@
   visitor->Trace(suggested_option_);
   visitor->Trace(descendant_selectedoptions_);
   visitor->Trace(select_type_);
+  visitor->Trace(descendants_observer_);
   HTMLFormControlElementWithState::Trace(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.h b/third_party/blink/renderer/core/html/forms/html_select_element.h
index 26dc164..ea1fa5c 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.h
@@ -51,6 +51,7 @@
 class V8UnionHTMLElementOrLong;
 class V8UnionHTMLOptGroupElementOrHTMLOptionElement;
 class HTMLSelectedOptionElement;
+class SelectDescendantsObserver;
 
 class CORE_EXPORT HTMLSelectElement final
     : public HTMLFormControlElementWithState,
@@ -382,6 +383,8 @@
   Member<SelectType> select_type_;
   int index_to_select_on_cancel_;
 
+  Member<SelectDescendantsObserver> descendants_observer_;
+
   friend class ListBoxSelectType;
   friend class MenuListSelectType;
   friend class SelectType;
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.idl b/third_party/blink/renderer/core/html/forms/html_select_element.idl
index 71a8b88b..3a7beb61 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.idl
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.idl
@@ -59,7 +59,7 @@
     readonly attribute NodeList labels;
 
     // https://html.spec.whatwg.org/multipage/input.html#dom-select-showpicker
-    [RaisesException, RuntimeEnabled=HTMLSelectElementShowPicker] void showPicker();
+    [RaisesException, RuntimeEnabled=HTMLSelectElementShowPicker, MeasureAs=ShowPickerSelect] void showPicker();
 
     [RuntimeEnabled=CustomizableSelect] attribute HTMLSelectedOptionElement? selectedOptionElement;
 };
diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser_fastpath.cc b/third_party/blink/renderer/core/html/parser/html_document_parser_fastpath.cc
index 7aee151..d13b2db 100644
--- a/third_party/blink/renderer/core/html/parser/html_document_parser_fastpath.cc
+++ b/third_party/blink/renderer/core/html/parser/html_document_parser_fastpath.cc
@@ -1262,8 +1262,7 @@
     QualifiedName name = LookupHTMLAttributeName(
         name_span.data(), static_cast<unsigned>(name_span.size()));
     if (name == g_null_name) {
-      name = QualifiedName(AtomicString(
-          name_span.data(), static_cast<unsigned>(name_span.size())));
+      name = QualifiedName(AtomicString(name_span));
     }
 
     // The string pointer in |value| is null for attributes with no values, but
@@ -1271,11 +1270,9 @@
     // no values have the value set to an empty atom instead.
     AtomicString value;
     if (value_span.second.empty()) {
-      value = AtomicString(value_span.first.data(),
-                           static_cast<unsigned>(value_span.first.size()));
+      value = AtomicString(value_span.first);
     } else {
-      value = AtomicString(value_span.second.data(),
-                           static_cast<unsigned>(value_span.second.size()));
+      value = AtomicString(value_span.second);
     }
     if (value.IsNull()) {
       value = g_empty_atom;
diff --git a/third_party/blink/renderer/core/html/parser/literal_buffer.h b/third_party/blink/renderer/core/html/parser/literal_buffer.h
index 5da0cb7..23a037c 100644
--- a/third_party/blink/renderer/core/html/parser/literal_buffer.h
+++ b/third_party/blink/renderer/core/html/parser/literal_buffer.h
@@ -266,9 +266,9 @@
   }
 
   AtomicString AsAtomicString() const {
-    return AtomicString(this->data(), this->size(),
-                        Is8Bit() ? WTF::AtomicStringUCharEncoding::kIs8Bit
-                                 : WTF::AtomicStringUCharEncoding::kIs16Bit);
+    return AtomicString(*this, Is8Bit()
+                                   ? WTF::AtomicStringUCharEncoding::kIs8Bit
+                                   : WTF::AtomicStringUCharEncoding::kIs16Bit);
   }
 
   ALWAYS_INLINE bool Is8Bit() const { return is_8bit_; }
diff --git a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
index 5587a8f..1965767 100644
--- a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
+++ b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
@@ -179,6 +179,9 @@
     parsed_options.resize_quality = cc::PaintFlags::FilterQuality::kNone;
   else
     parsed_options.resize_quality = cc::PaintFlags::FilterQuality::kLow;
+
+  parsed_options.sampling = cc::PaintFlags::FilterQualityToSkSamplingOptions(
+      parsed_options.resize_quality);
   return parsed_options;
 }
 
@@ -229,6 +232,31 @@
   return input->PaintImageForCurrentFrame().GetSkImageInfo();
 }
 
+void FlipSkPixmapInPlace(SkPixmap& pm, bool horizontal) {
+  uint8_t* data = reinterpret_cast<uint8_t*>(pm.writable_addr());
+  const size_t row_bytes = pm.rowBytes();
+  const size_t pixel_bytes = pm.info().bytesPerPixel();
+  if (horizontal) {
+    for (int i = 0; i < pm.height() - 1; i++) {
+      for (int j = 0; j < pm.width() / 2; j++) {
+        size_t first_element = i * row_bytes + j * pixel_bytes;
+        size_t last_element = i * row_bytes + (j + 1) * pixel_bytes;
+        size_t bottom_element = (i + 1) * row_bytes - (j + 1) * pixel_bytes;
+        std::swap_ranges(&data[first_element], &data[last_element],
+                         &data[bottom_element]);
+      }
+    }
+  } else {
+    for (int i = 0; i < pm.height() / 2; i++) {
+      size_t top_first_element = i * row_bytes;
+      size_t top_last_element = (i + 1) * row_bytes;
+      size_t bottom_first_element = (pm.height() - 1 - i) * row_bytes;
+      std::swap_ranges(&data[top_first_element], &data[top_last_element],
+                       &data[bottom_first_element]);
+    }
+  }
+}
+
 static inline bool ShouldAvoidPremul(
     const ImageBitmap::ParsedOptions& options) {
   return options.source_is_unpremul && !options.premultiply_alpha;
@@ -262,6 +290,149 @@
                                                       kShouldInitialize);
 }
 
+// Perform the requested transformations on the CPU.
+scoped_refptr<StaticBitmapImage> ApplyTransformsFromOptionsSoftware(
+    scoped_refptr<StaticBitmapImage> source,
+    const ImageBitmap::ParsedOptions& options) {
+  auto source_paint_image = source->PaintImageForCurrentFrame();
+  auto source_info = source->GetSkImageInfo();
+
+  // Compute the unoriented source and dest rects and sizes.
+  SkIRect source_rect;
+  SkIRect source_rect_valid;
+  SkISize dest_size;
+  options.ComputeSubsetParameters(source_rect, source_rect_valid, dest_size);
+
+  // Let `bm` be the image that we're manipulating step-by-step.
+  SkBitmap bm;
+
+  // Allocate the cropped source image.
+  {
+    SkAlphaType bm_alpha_type = source_info.alphaType();
+    if (bm_alpha_type != kOpaque_SkAlphaType) {
+      if (options.premultiply_alpha) {
+        bm_alpha_type = kPremul_SkAlphaType;
+      } else {
+        bm_alpha_type = kUnpremul_SkAlphaType;
+      }
+    }
+    const auto bm_info = source_info.makeDimensions(source_rect.size())
+                             .makeAlphaType(bm_alpha_type);
+    if (!bm.tryAllocPixels(bm_info)) {
+      return nullptr;
+    }
+  }
+
+  // Populate the cropped image by calling `readPixels`. This can also do alpha
+  // conversion.
+  {
+    // Let `pm_valid_rect` be the intersection of `source_rect` with
+    // `source_size`. It will be a subset of `bm`, and we wil read into it.
+    SkIRect pm_valid_rect = SkIRect::MakeXYWH(
+        source_rect_valid.x() - source_rect.x(),
+        source_rect_valid.y() - source_rect.y(), source_rect_valid.width(),
+        source_rect_valid.height());
+    SkPixmap pm_valid;
+    if (!source_rect_valid.isEmpty() &&
+        !bm.pixmap().extractSubset(&pm_valid, pm_valid_rect)) {
+      NOTREACHED();
+    }
+    if (!source_rect_valid.isEmpty()) {
+      if (!source_paint_image.readPixels(
+              pm_valid.info(), pm_valid.writable_addr(), pm_valid.rowBytes(),
+              source_rect_valid.x(), source_rect_valid.y())) {
+        return nullptr;
+      }
+    }
+  }
+
+  // Apply scaling.
+  if (bm.dimensions() != dest_size) {
+    SkBitmap bm_scaled;
+    if (!bm_scaled.tryAllocPixels(bm.info().makeDimensions(dest_size))) {
+      return nullptr;
+    }
+    bm.pixmap().scalePixels(bm_scaled.pixmap(), options.sampling);
+    bm = bm_scaled;
+  }
+
+  // Apply vertical flip by using a different ImageOrientation.
+  if (options.flip_y) {
+    SkPixmap pm = bm.pixmap();
+    FlipSkPixmapInPlace(pm, options.source_orientation.UsesWidthAsHeight());
+  }
+
+  // Create the resulting SkImage.
+  bm.setImmutable();
+  auto dest_image = bm.asImage();
+  ;
+
+  // Strip the color space if requested.
+  if (!options.has_color_space_conversion) {
+    dest_image = dest_image->reinterpretColorSpace(SkColorSpace::MakeSRGB());
+  }
+
+  // Return the result.
+  auto dest_paint_image =
+      PaintImageBuilder::WithDefault()
+          .set_id(source_paint_image.stable_id())
+          .set_image(std::move(dest_image),
+                     source_paint_image.GetContentIdForFrame(0u))
+          .TakePaintImage();
+  return UnacceleratedStaticBitmapImage::Create(std::move(dest_paint_image),
+                                                options.source_orientation);
+}
+
+// Perform all transformations using a blit, which will result in a new
+// premultiplied-alpha result.
+scoped_refptr<StaticBitmapImage> ApplyTransformsFromOptionsWithBlit(
+    scoped_refptr<StaticBitmapImage> src,
+    const ImageBitmap::ParsedOptions& options) {
+  // This path will necessarily premultiply alpha. If unmultiplied alpha is
+  // requested and the source was originally unmultiplied, then we this would
+  // break things.
+  CHECK(!options.MustPreserveUnpremulValues());
+
+  auto paint_image = src->PaintImageForCurrentFrame();
+
+  // Compute the parameters for the blit.
+  const SkAlphaType dst_alpha_type =
+      paint_image.GetAlphaType() == kOpaque_SkAlphaType ? kOpaque_SkAlphaType
+                                                        : kPremul_SkAlphaType;
+  auto dst_color_space = paint_image.GetSkImageInfo().refColorSpace();
+  SkIRect source_rect;
+  SkIRect source_rect_valid;
+  SkISize dest_size;
+  options.ComputeSubsetParameters(source_rect, source_rect_valid, dest_size);
+
+  SkImageInfo dst_info = SkImageInfo::Make(dest_size, kN32_SkColorType,
+                                           dst_alpha_type, dst_color_space);
+  auto resource_provider = CreateProvider(
+      paint_image.IsTextureBacked() ? src->ContextProviderWrapper() : nullptr,
+      dst_info, src, true /* fallback_to_software */);
+  if (!resource_provider) {
+    return nullptr;
+  }
+
+  cc::PaintFlags paint;
+  paint.setBlendMode(SkBlendMode::kSrc);
+  cc::PaintCanvas& canvas = resource_provider->Canvas();
+  if (options.flip_y) {
+    if (options.source_orientation.UsesWidthAsHeight()) {
+      canvas.translate(dest_size.width(), 0);
+      canvas.scale(-1, 1);
+    } else {
+      canvas.translate(0, dest_size.height());
+      canvas.scale(1, -1);
+    }
+  }
+  canvas.drawImageRect(paint_image, SkRect::Make(source_rect),
+                       SkRect::Make(dest_size), options.sampling, &paint,
+                       SkCanvas::kStrict_SrcRectConstraint);
+  return resource_provider->Snapshot(FlushReason::kCreateImageBitmap,
+                                     options.source_orientation);
+}
+
 scoped_refptr<StaticBitmapImage> FlipImageVertically(
     scoped_refptr<StaticBitmapImage> input,
     const ImageBitmap::ParsedOptions& parsed_options) {
@@ -485,6 +656,33 @@
                                      input->CurrentFrameOrientation());
 }
 
+scoped_refptr<StaticBitmapImage> ApplyTransformsFromOptions(
+    scoped_refptr<StaticBitmapImage> source,
+    const ImageBitmap::ParsedOptions& options) {
+  const bool needs_flip = options.flip_y;
+  const bool needs_crop = options.source_rect != gfx::Rect(options.source_size);
+  const bool needs_resize = options.source_rect.size() != options.dest_size;
+  const bool needs_strip_orientation = !options.orientation_from_image;
+  const bool needs_strip_color_space = !options.has_color_space_conversion;
+  const bool needs_alpha_change =
+      options.source_is_unpremul != (!options.premultiply_alpha);
+
+  // If we aren't doing anything, just return the original.
+  if (!needs_flip && !needs_crop && !needs_resize && !needs_strip_orientation &&
+      !needs_strip_color_space && !needs_alpha_change) {
+    return source;
+  }
+
+  // Using a blit will premultiply content, so if unpremultiplied results are
+  // requested, fall back to software. The test ImageBitmapTest.AvoidGPUReadback
+  // expects this, even if the source had premultiplied alpha (in which case we
+  // are falling back to the CPU for no increased precision).
+  if (!options.premultiply_alpha) {
+    return ApplyTransformsFromOptionsSoftware(source, options);
+  }
+  return ApplyTransformsFromOptionsWithBlit(source, options);
+}
+
 scoped_refptr<StaticBitmapImage> MakeBlankImage(
     const ImageBitmap::ParsedOptions& parsed_options) {
   SkImageInfo info = SkImageInfo::Make(
@@ -593,8 +791,32 @@
 
   return result;
 }
+
 }  // namespace
 
+void ImageBitmap::ParsedOptions::ComputeSubsetParameters(
+    SkIRect& source_skrect,
+    SkIRect& source_skrect_valid,
+    SkISize& dest_sksize) const {
+  gfx::Size unoriented_source_size = source_size;
+  gfx::Size unoriented_dest_size = dest_size;
+  if (source_orientation.UsesWidthAsHeight()) {
+    unoriented_source_size = gfx::TransposeSize(unoriented_source_size);
+    unoriented_dest_size = gfx::TransposeSize(unoriented_dest_size);
+  }
+  auto t = source_orientation.TransformFromDefault(
+      gfx::SizeF(unoriented_source_size));
+  const gfx::Rect source_rect_valid =
+      gfx::IntersectRects(source_rect, gfx::Rect(source_size));
+
+  const gfx::Rect unoriented_source_rect = t.MapRect(source_rect);
+  const gfx::Rect unoriented_source_rect_valid = t.MapRect(source_rect_valid);
+
+  source_skrect = gfx::RectToSkIRect(unoriented_source_rect);
+  source_skrect_valid = gfx::RectToSkIRect(unoriented_source_rect_valid);
+  dest_sksize = gfx::SizeToSkISize(unoriented_dest_size);
+}
+
 sk_sp<SkImage> ImageBitmap::GetSkImageFromDecoder(
     std::unique_ptr<ImageDecoder> decoder) {
   if (!decoder->FrameCount())
@@ -671,8 +893,8 @@
 
   auto static_input = UnacceleratedStaticBitmapImage::Create(
       std::move(paint_image), input->CurrentFrameOrientation());
-  image_ = CropImageAndApplyColorSpaceConversion(std::move(static_input),
-                                                 parsed_options);
+
+  image_ = ApplyTransformsFromOptions(static_input, parsed_options);
   if (!image_)
     return;
 
@@ -886,8 +1108,7 @@
   if (DstBufferSizeHasOverflow(parsed_options))
     return;
 
-  image_ =
-      CropImageAndApplyColorSpaceConversion(std::move(image), parsed_options);
+  image_ = ApplyTransformsFromOptions(image, parsed_options);
   if (!image_)
     return;
 
diff --git a/third_party/blink/renderer/core/imagebitmap/image_bitmap.h b/third_party/blink/renderer/core/imagebitmap/image_bitmap.h
index 902305b5..94e3e4a1 100644
--- a/third_party/blink/renderer/core/imagebitmap/image_bitmap.h
+++ b/third_party/blink/renderer/core/imagebitmap/image_bitmap.h
@@ -131,6 +131,10 @@
                                                ExceptionState&) override;
 
   struct ParsedOptions {
+    bool MustPreserveUnpremulValues() const {
+      return source_is_unpremul && !premultiply_alpha;
+    }
+
     // If true, then the final result should be flipped vertically. This happens
     // in the space after `source_orientation` has been applied.
     bool flip_y = false;
@@ -150,12 +154,11 @@
     cc::PaintFlags::FilterQuality resize_quality =
         cc::PaintFlags::FilterQuality::kLow;
 
-    // The filter quality specified by the options. This is always set, even
-    // if no resampling is needed.
-    cc::PaintFlags::FilterQuality filter_quality =
-        cc::PaintFlags::FilterQuality::kLow;
+    // The sampling options to use. This will be set to nearest-neighbor if no
+    // resampling is performed.
+    SkSamplingOptions sampling;
 
-    // The orientation of the source. This may be from the source or overridden.
+    // The orientation of the source.
     class ImageOrientation source_orientation;
 
     // The `source_size`, `source_rect`, and `dest_size` parameters are all in
@@ -163,6 +166,15 @@
     gfx::Size source_size;
     gfx::Rect source_rect;
     gfx::Size dest_size;
+
+    // Compute the parameters for creating and then resizing a subset of the
+    // source image. In the underlying PaintImage, `source_skrect` corresponds
+    // to `source_rect`, `source_skrect_valid` corresponds to the intersection
+    // of that with the PaintImage size, and `dest_sksize` corresponds to the
+    // output size.
+    void ComputeSubsetParameters(SkIRect& source_skrect,
+                                 SkIRect& source_skrect_valid,
+                                 SkISize& dest_sksize) const;
   };
 
  private:
diff --git a/third_party/blink/renderer/core/layout/absolute_utils.cc b/third_party/blink/renderer/core/layout/absolute_utils.cc
index d88ce3f..19ab2d3 100644
--- a/third_party/blink/renderer/core/layout/absolute_utils.cc
+++ b/third_party/blink/renderer/core/layout/absolute_utils.cc
@@ -703,8 +703,7 @@
     const Length& auto_length = ([&]() {
       // Tables always shrink-to-fit unless explicitly asked to stretch.
       if (node.IsTable()) {
-        return is_explicit_stretch ? Length::FillAvailable()
-                                   : Length::FitContent();
+        return is_explicit_stretch ? Length::Stretch() : Length::FitContent();
       }
       // We'd like to apply the aspect-ratio.
       // The aspect-ratio applies from the block-axis if we can compute our
@@ -722,7 +721,7 @@
         }
         return Length::FitContent();
       }
-      return is_stretch ? Length::FillAvailable() : Length::FitContent();
+      return is_stretch ? Length::Stretch() : Length::FitContent();
     })();
 
     const LayoutUnit main_inline_size = ResolveMainInlineLength(
@@ -806,8 +805,8 @@
     // Nothing depends on our intrinsic-size, so we can safely use the initial
     // variant of these functions.
     const LayoutUnit main_block_size = ResolveMainBlockLength(
-        space, style, border_padding, style.LogicalHeight(),
-        &Length::FillAvailable(), kIndefiniteSize, imcb.BlockSize());
+        space, style, border_padding, style.LogicalHeight(), &Length::Stretch(),
+        kIndefiniteSize, imcb.BlockSize());
     const MinMaxSizes min_max_block_sizes =
         ComputeInitialMinMaxBlockSizes(space, node, border_padding);
     block_size = min_max_block_sizes.ClampSizeToMinAndMax(main_block_size);
diff --git a/third_party/blink/renderer/core/layout/column_layout_algorithm.cc b/third_party/blink/renderer/core/layout/column_layout_algorithm.cc
index 50e120a..f69479a 100644
--- a/third_party/blink/renderer/core/layout/column_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/column_layout_algorithm.cc
@@ -1479,7 +1479,7 @@
 
   const Length& block_length = style.LogicalHeight();
   const Length& auto_length = space.IsBlockAutoBehaviorStretch()
-                                  ? Length::FillAvailable()
+                                  ? Length::Stretch()
                                   : Length::FitContent();
 
   extent = ResolveMainBlockLength(space, style, BorderPadding(), block_length,
diff --git a/third_party/blink/renderer/core/layout/exclusions/exclusion_space.cc b/third_party/blink/renderer/core/layout/exclusions/exclusion_space.cc
index 6d6a031..a86d79f 100644
--- a/third_party/blink/renderer/core/layout/exclusions/exclusion_space.cc
+++ b/third_party/blink/renderer/core/layout/exclusions/exclusion_space.cc
@@ -2,11 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
-#pragma allow_unsafe_buffers
-#endif
-
 #include "third_party/blink/renderer/core/layout/exclusions/exclusion_space.h"
 
 #include <algorithm>
@@ -92,13 +87,10 @@
                        LayoutUnit block_offset,
                        Vector<ExclusionSpaceInternal::ShelfEdge>* out_edges) {
   *out_edges = std::move(*edges);
-  for (auto it = out_edges->begin(); it != out_edges->end();) {
-    if ((*it).block_end <= block_offset) {
-      it = out_edges->erase(it);
-    } else {
-      ++it;
-    }
-  }
+  auto it = std::remove_if(
+      out_edges->begin(), out_edges->end(),
+      [&](const auto& edge) { return edge.block_end <= block_offset; });
+  out_edges->erase(it, out_edges->end());
 }
 
 // Returns true if the area defined by the given offset and inline_size
@@ -251,8 +243,7 @@
       // Perform a copy-on-write if the number of exclusions has gone out of
       // sync.
       auto* exclusions = MakeGarbageCollected<ExclusionAreaPtrArray>();
-      exclusions->AppendRange(exclusions_->begin(),
-                              exclusions_->begin() + num_exclusions_);
+      exclusions->AppendSpan(base::span(*exclusions_).first(num_exclusions_));
       exclusions_ = exclusions;
     }
   }
@@ -296,12 +287,15 @@
       const auto& source_exclusions = *exclusions_;
       exclusions_ = MakeGarbageCollected<ExclusionAreaPtrArray>();
       exclusions_->resize(num_exclusions_ + 1);
-      const auto source_end = source_exclusions.begin() + num_exclusions_;
+      const auto source_span =
+          base::span(source_exclusions).first(num_exclusions_);
+      const auto source_end = source_span.end();
       // Initial-letters are special in that they can be inserted "before"
       // other floats. Ensure we insert |exclusion| in the correct place
       // (ascent order by block-start).
-      auto destination = exclusions_->begin();
-      for (auto it = source_exclusions.begin(); it != source_end; ++it) {
+      auto destination_span = base::span(*exclusions_);
+      auto destination = destination_span.begin();
+      for (auto it = source_span.begin(); it != source_end; ++it) {
         if (exclusion->rect.BlockStartOffset() <
             (*it)->rect.BlockStartOffset()) {
           *destination = exclusion;
@@ -310,9 +304,10 @@
         }
         *destination++ = *it;
       }
-      if (destination != exclusions_->end())
+      if (destination != destination_span.end()) {
         *destination++ = exclusion;
-      DCHECK_EQ(destination, exclusions_->end());
+      }
+      DCHECK(destination == destination_span.end());
     }
     num_exclusions_++;
 
@@ -695,16 +690,17 @@
     const BfcOffset& offset,
     const LayoutUnit available_inline_size,
     const LambdaFunc& lambda) const {
-  auto shelves_it = shelves_.begin();
-  auto areas_it = areas_.begin();
-
-  auto const shelves_end = shelves_.end();
-  auto const areas_end = areas_.end();
+  auto shelves_span = base::span(shelves_);
+  auto areas_span = base::span(areas_);
+  auto shelves_it = shelves_span.begin();
+  auto areas_it = areas_span.begin();
+  auto const shelves_end = shelves_span.end();
+  auto const areas_end = areas_span.end();
 
   while (shelves_it != shelves_end || areas_it != areas_end) {
     // We should never exhaust the opportunities list before the shelves list,
     // as there is always an infinitely sized shelf at the very end.
-    DCHECK_NE(shelves_it, shelves_end);
+    DCHECK(shelves_it != shelves_end);
     const Shelf& shelf = *shelves_it;
 
     if (areas_it != areas_end) {
@@ -767,9 +763,9 @@
     DCHECK_LE(num_exclusions_, exclusions_->size());
     DCHECK_GE(num_exclusions_, 1u);
 
-    const auto begin = exclusions_->begin();
-    const auto end = begin + num_exclusions_;
-    DCHECK_LE(end, exclusions_->end());
+    auto span = base::span(*exclusions_).first(num_exclusions_);
+    const auto begin = span.begin();
+    const auto end = span.end();
 
     // Find the first exclusion whose block-start offset is "after" the
     // |block_offset_limit|.
diff --git a/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc
index 692787d4..437f62a 100644
--- a/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc
@@ -619,21 +619,15 @@
     const LayoutResult* layout_result = nullptr;
     auto BlockSizeFunc = [&](SizeType type) -> LayoutUnit {
       // This function mirrors the logic within `BlockNode::ComputeMinMaxSizes`.
-      if (!layout_result) {
-        ConstraintSpace child_space =
-            BuildSpaceForIntrinsicBlockSize(child, max_content_contribution);
-        std::optional<DisableLayoutSideEffectsScope> disable_side_effects;
-        if (phase != Phase::kLayout && !Node().GetLayoutBox()->NeedsLayout()) {
-          disable_side_effects.emplace();
-        }
-        layout_result = child.Layout(child_space, /* break_token */ nullptr);
-        DCHECK(layout_result);
-      }
+      const ConstraintSpace child_space =
+          BuildSpaceForIntrinsicBlockSize(child, max_content_contribution);
 
       // Don't apply any special aspect-ratio treatment for replaced elements.
-      const LayoutUnit intrinsic_size = layout_result->IntrinsicBlockSize();
       if (child.IsReplaced()) {
-        return intrinsic_size;
+        return ComputeReplacedSize(child, child_space,
+                                   border_padding_in_child_writing_mode,
+                                   ReplacedSizeMode::kIgnoreBlockLengths)
+            .block_size;
       }
 
       const bool has_aspect_ratio = !child_style.AspectRatio().IsAuto();
@@ -646,6 +640,16 @@
         }
       }
 
+      if (!layout_result) {
+        std::optional<DisableLayoutSideEffectsScope> disable_side_effects;
+        if (phase != Phase::kLayout && !Node().GetLayoutBox()->NeedsLayout()) {
+          disable_side_effects.emplace();
+        }
+        layout_result = child.Layout(child_space, /* break_token */ nullptr);
+        DCHECK(layout_result);
+      }
+      const LayoutUnit intrinsic_size = layout_result->IntrinsicBlockSize();
+
       // Constrain the intrinsic-size by the transferred min/max constraints.
       if (has_aspect_ratio) {
         const MinMaxSizes inline_min_max = ComputeMinMaxInlineSizes(
diff --git a/third_party/blink/renderer/core/layout/grid/grid_break_token_data.h b/third_party/blink/renderer/core/layout/grid/grid_break_token_data.h
index c9fd447..e06af98 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_break_token_data.h
+++ b/third_party/blink/renderer/core/layout/grid/grid_break_token_data.h
@@ -6,13 +6,12 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_GRID_GRID_BREAK_TOKEN_DATA_H_
 
 #include "third_party/blink/renderer/core/layout/block_break_token_data.h"
-#include "third_party/blink/renderer/core/layout/fragmentation_utils.h"
-#include "third_party/blink/renderer/core/layout/grid/grid_sizing_tree.h"
-#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 
+class GridSizingTree;
+
 struct GridItemPlacementData {
   GridItemPlacementData(
       LogicalOffset offset,
diff --git a/third_party/blink/renderer/core/layout/grid/grid_item.h b/third_party/blink/renderer/core/layout/grid/grid_item.h
index 47ecb0d..e763a12 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_item.h
+++ b/third_party/blink/renderer/core/layout/grid/grid_item.h
@@ -15,7 +15,6 @@
 namespace blink {
 
 enum class AxisEdge { kStart, kCenter, kEnd, kFirstBaseline, kLastBaseline };
-enum class SizingConstraint { kLayout, kMinContent, kMaxContent };
 
 struct GridItemIndices {
   wtf_size_t begin{kNotFound};
diff --git a/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc b/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc
index 15030f5..ea74b46 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc
@@ -12,10 +12,11 @@
 #include "third_party/blink/renderer/core/layout/constraint_space_builder.h"
 #include "third_party/blink/renderer/core/layout/disable_layout_side_effects_scope.h"
 #include "third_party/blink/renderer/core/layout/fragmentation_utils.h"
+#include "third_party/blink/renderer/core/layout/grid/grid_break_token_data.h"
+#include "third_party/blink/renderer/core/layout/grid/grid_item.h"
 #include "third_party/blink/renderer/core/layout/length_utils.h"
 #include "third_party/blink/renderer/core/layout/logical_box_fragment.h"
 #include "third_party/blink/renderer/core/layout/relative_utils.h"
-#include "third_party/blink/renderer/core/layout/space_utils.h"
 
 namespace blink {
 
@@ -1269,7 +1270,7 @@
       switch (main_length.GetType()) {
         case Length::kAuto:
         case Length::kFitContent:
-        case Length::kFillAvailable:
+        case Length::kStretch:
         case Length::kPercent:
         case Length::kCalculated: {
           const auto border_padding =
diff --git a/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.h b/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.h
index 5d534f9..587a922 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.h
@@ -8,16 +8,16 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/block_break_token.h"
 #include "third_party/blink/renderer/core/layout/box_fragment_builder.h"
-#include "third_party/blink/renderer/core/layout/constraint_space.h"
-#include "third_party/blink/renderer/core/layout/grid/grid_break_token_data.h"
 #include "third_party/blink/renderer/core/layout/grid/grid_node.h"
-#include "third_party/blink/renderer/core/layout/grid/grid_placement.h"
 #include "third_party/blink/renderer/core/layout/grid/grid_sizing_tree.h"
 #include "third_party/blink/renderer/core/layout/layout_algorithm.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 
+class ConstraintSpace;
+struct GridItemPlacementData;
+
 // This enum corresponds to each step used to accommodate grid items across
 // intrinsic tracks according to their min and max track sizing functions, as
 // defined in https://drafts.csswg.org/css-grid-2/#algo-spanning-items.
@@ -30,6 +30,8 @@
   kForFreeSpace,
 };
 
+enum class SizingConstraint { kLayout, kMinContent, kMaxContent };
+
 using GridItemDataPtrVector = Vector<GridItemData*, 16>;
 using GridSetPtrVector = Vector<GridSet*, 16>;
 
diff --git a/third_party/blink/renderer/core/layout/grid/grid_node.h b/third_party/blink/renderer/core/layout/grid/grid_node.h
index a1b8de4..c25a5f3d 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_node.h
+++ b/third_party/blink/renderer/core/layout/grid/grid_node.h
@@ -7,11 +7,11 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/block_node.h"
-#include "third_party/blink/renderer/core/layout/grid/grid_item.h"
 #include "third_party/blink/renderer/core/layout/grid/layout_grid.h"
 
 namespace blink {
 
+class GridItems;
 class GridSizingSubtree;
 
 // Grid specific extensions to `BlockNode`.
diff --git a/third_party/blink/renderer/core/layout/grid/grid_placement.cc b/third_party/blink/renderer/core/layout/grid/grid_placement.cc
index 6e4c6ffc..0f83804 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_placement.cc
+++ b/third_party/blink/renderer/core/layout/grid/grid_placement.cc
@@ -4,7 +4,7 @@
 
 #include "third_party/blink/renderer/core/layout/grid/grid_placement.h"
 
-#include "third_party/blink/renderer/core/layout/grid/grid_line_resolver.h"
+#include "third_party/blink/renderer/core/layout/grid/grid_item.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/layout/grid/grid_placement.h b/third_party/blink/renderer/core/layout/grid/grid_placement.h
index b9999b3da..42a6a53 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_placement.h
+++ b/third_party/blink/renderer/core/layout/grid/grid_placement.h
@@ -7,13 +7,13 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/grid/grid_data.h"
-#include "third_party/blink/renderer/core/layout/grid/grid_item.h"
-#include "third_party/blink/renderer/core/layout/grid/grid_track_collection.h"
 #include "third_party/blink/renderer/platform/wtf/doubly_linked_list.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 
+class GridItems;
+
 // This class encapsulates the Grid Item Placement Algorithm described by
 // https://drafts.csswg.org/css-grid/#auto-placement-algo
 class CORE_EXPORT GridPlacement {
diff --git a/third_party/blink/renderer/core/layout/grid/grid_sizing_tree.h b/third_party/blink/renderer/core/layout/grid/grid_sizing_tree.h
index d6b9d66..368cfdd 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_sizing_tree.h
+++ b/third_party/blink/renderer/core/layout/grid/grid_sizing_tree.h
@@ -15,9 +15,9 @@
 // algorithm of an ancestor grid that may not be its parent grid.
 //
 // For a given subgridded item, this class encapsulates a pointer to its
-// |GridItemData| in the context of its parent grid (i.e., its properties are
+// `GridItemData` in the context of its parent grid (i.e., its properties are
 // relative to its parent's area and writing mode) and a pointer to the actual
-// |GridLayoutData| of the grid that directly contains the subgridded item.
+// `GridLayoutData` of the grid that directly contains the subgridded item.
 class SubgriddedItemData {
   DISALLOW_NEW();
 
@@ -182,7 +182,7 @@
 
     return GridSizingSubtree(
         *grid_tree_,
-        /* subtree_root */ grid_tree_->LookupSubgridIndex(subgrid_data.node));
+        /*subtree_root=*/grid_tree_->LookupSubgridIndex(subgrid_data.node));
   }
 
   // This method is only intended to be used to validate that the given grid
@@ -214,4 +214,7 @@
 
 }  // namespace blink
 
+WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS(
+    blink::GridSizingTree::GridTreeNode)
+
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_GRID_GRID_SIZING_TREE_H_
diff --git a/third_party/blink/renderer/core/layout/grid/grid_track_collection.h b/third_party/blink/renderer/core/layout/grid/grid_track_collection.h
index b734a08e..aad28da9 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_track_collection.h
+++ b/third_party/blink/renderer/core/layout/grid/grid_track_collection.h
@@ -8,7 +8,7 @@
 #include "base/check_op.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
-#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/layout/inline/initial_letter_utils.cc b/third_party/blink/renderer/core/layout/inline/initial_letter_utils.cc
index c8b537d3..2c815145 100644
--- a/third_party/blink/renderer/core/layout/inline/initial_letter_utils.cc
+++ b/third_party/blink/renderer/core/layout/inline/initial_letter_utils.cc
@@ -35,8 +35,10 @@
   // English font.
   const LayoutUnit line_height = paragraph_style.ComputedLineHeightAsFixed();
 
-  const int size = std::ceil(block_size / line_height.ToFloat());
-
+  const int size =
+      std::ceil(RuntimeEnabledFeatures::InitialLetterRaiseBySpecifiedEnabled()
+                    ? initial_letter.Size()
+                    : block_size / line_height.ToFloat());
   const int sink = initial_letter.IsRaise() || initial_letter.IsIntegerSink()
                        ? initial_letter.Sink()
                        : size;
diff --git a/third_party/blink/renderer/core/layout/length_utils.cc b/third_party/blink/renderer/core/layout/length_utils.cc
index 64479696..9518cd6 100644
--- a/third_party/blink/renderer/core/layout/length_utils.cc
+++ b/third_party/blink/renderer/core/layout/length_utils.cc
@@ -41,7 +41,7 @@
   const Length& length =
       original_length.IsAuto() && auto_length ? *auto_length : original_length;
   switch (length.GetType()) {
-    case Length::kFillAvailable: {
+    case Length::kStretch: {
       const LayoutUnit available_size =
           override_available_size == kIndefiniteSize
               ? constraint_space.AvailableSize().inline_size
@@ -157,7 +157,7 @@
   const Length& length =
       original_length.IsAuto() && auto_length ? *auto_length : original_length;
   switch (length.GetType()) {
-    case Length::kFillAvailable: {
+    case Length::kStretch: {
       const LayoutUnit available_size =
           override_available_size == kIndefiniteSize
               ? constraint_space.AvailableSize().block_size
@@ -498,13 +498,13 @@
       return Length::MinContent();
     }
     if (space.InlineAutoBehavior() == AutoSizeBehavior::kStretchExplicit) {
-      return Length::FillAvailable();
+      return Length::Stretch();
     }
     if (may_apply_aspect_ratio) {
       return Length::FitContent();
     }
     if (space.InlineAutoBehavior() == AutoSizeBehavior::kStretchImplicit) {
-      return Length::FillAvailable();
+      return Length::Stretch();
     }
     DCHECK_EQ(space.InlineAutoBehavior(), AutoSizeBehavior::kFitContent);
     return Length::FitContent();
@@ -762,13 +762,13 @@
       return Length::FitContent();
     }
     if (space.BlockAutoBehavior() == AutoSizeBehavior::kStretchExplicit) {
-      return Length::FillAvailable();
+      return Length::Stretch();
     }
     if (may_apply_aspect_ratio) {
       return Length::FitContent();
     }
     if (space.BlockAutoBehavior() == AutoSizeBehavior::kStretchImplicit) {
-      return Length::FillAvailable();
+      return Length::Stretch();
     }
     DCHECK_EQ(space.BlockAutoBehavior(), AutoSizeBehavior::kFitContent);
     return Length::FitContent();
@@ -1004,7 +1004,7 @@
                (space.IsBlockAutoBehaviorStretch() &&
                 space.AvailableSize().block_size != kIndefiniteSize)) {
       const Length& block_length_to_resolve =
-          block_length.HasAuto() ? Length::FillAvailable() : block_length;
+          block_length.HasAuto() ? Length::Stretch() : block_length;
 
       const LayoutUnit main_percentage_resolution_size =
           space.ReplacedPercentageResolutionBlockSize();
@@ -1044,7 +1044,7 @@
             NOTREACHED_IN_MIGRATION();
             return MinMaxSizesResult();
           },
-          Length::FillAvailable(), /* auto_length */ nullptr,
+          Length::Stretch(), /* auto_length */ nullptr,
           /* override_available_size */ kIndefiniteSize);
     }
 
@@ -1103,7 +1103,7 @@
                (space.IsInlineAutoBehaviorStretch() &&
                 space.AvailableSize().inline_size != kIndefiniteSize)) {
       const Length& auto_length = space.IsInlineAutoBehaviorStretch()
-                                      ? Length::FillAvailable()
+                                      ? Length::Stretch()
                                       : Length::FitContent();
       const LayoutUnit inline_size =
           ResolveMainInlineLength(space, style, border_padding, MinMaxSizesFunc,
diff --git a/third_party/blink/renderer/core/layout/length_utils_test.cc b/third_party/blink/renderer/core/layout/length_utils_test.cc
index 3b2b1f4..6becd9a 100644
--- a/third_party/blink/renderer/core/layout/length_utils_test.cc
+++ b/third_party/blink/renderer/core/layout/length_utils_test.cc
@@ -120,7 +120,7 @@
 TEST_F(LengthUtilsTest, TestResolveInlineLength) {
   EXPECT_EQ(LayoutUnit(60), ResolveMainInlineLength(Length::Percent(30)));
   EXPECT_EQ(LayoutUnit(150), ResolveMainInlineLength(Length::Fixed(150)));
-  EXPECT_EQ(LayoutUnit(200), ResolveMainInlineLength(Length::FillAvailable()));
+  EXPECT_EQ(LayoutUnit(200), ResolveMainInlineLength(Length::Stretch()));
 
   MinMaxSizes sizes;
   sizes.min_size = LayoutUnit(30);
@@ -148,14 +148,14 @@
             ResolveMinInlineLength(Length::Auto(), std::nullopt, space));
   EXPECT_EQ(LayoutUnit::Max(),
             ResolveMaxInlineLength(Length::Percent(30), std::nullopt, space));
-  EXPECT_EQ(LayoutUnit::Max(), ResolveMaxInlineLength(Length::FillAvailable(),
-                                                      std::nullopt, space));
+  EXPECT_EQ(LayoutUnit::Max(),
+            ResolveMaxInlineLength(Length::Stretch(), std::nullopt, space));
 }
 
 TEST_F(LengthUtilsTest, TestResolveBlockLength) {
   EXPECT_EQ(LayoutUnit(90), ResolveMainBlockLength(Length::Percent(30)));
   EXPECT_EQ(LayoutUnit(150), ResolveMainBlockLength(Length::Fixed(150)));
-  EXPECT_EQ(LayoutUnit(300), ResolveMainBlockLength(Length::FillAvailable()));
+  EXPECT_EQ(LayoutUnit(300), ResolveMainBlockLength(Length::Stretch()));
 }
 
 TEST_F(LengthUtilsTestWithNode, TestComputeContentContribution) {
diff --git a/third_party/blink/renderer/core/layout/replaced_layout_algorithm.cc b/third_party/blink/renderer/core/layout/replaced_layout_algorithm.cc
index 0b7ce5d9..fa5482d 100644
--- a/third_party/blink/renderer/core/layout/replaced_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/replaced_layout_algorithm.cc
@@ -22,15 +22,6 @@
 
 const LayoutResult* ReplacedLayoutAlgorithm::Layout() {
   DCHECK(!GetBreakToken() || GetBreakToken()->IsBreakBefore());
-  // TODO(crbug.com/1252693): kIgnoreBlockLengths applies inline constraints
-  // through the aspect ratio. But the aspect ratio is ignored when computing
-  // the intrinsic block size for NON-replaced elements. This is inconsistent
-  // and could lead to subtle bugs.
-  const LayoutUnit intrinsic_block_size =
-      ComputeReplacedSize(Node(), GetConstraintSpace(), BorderPadding(),
-                          ReplacedSizeMode::kIgnoreBlockLengths)
-          .block_size;
-  container_builder_.SetIntrinsicBlockSize(intrinsic_block_size);
 
   if (Node().IsMedia()) {
     LayoutMediaChildren();
diff --git a/third_party/blink/renderer/core/lcp_critical_path_predictor/element_locator.cc b/third_party/blink/renderer/core/lcp_critical_path_predictor/element_locator.cc
index f371f3e2..61ccbb3 100644
--- a/third_party/blink/renderer/core/lcp_critical_path_predictor/element_locator.cc
+++ b/third_party/blink/renderer/core/lcp_critical_path_predictor/element_locator.cc
@@ -225,9 +225,7 @@
 
       case ElementLocator_Component::kNth: {
         const std::string& tag_name_stdstr = c.nth().tag_name();
-        AtomicString tag_name(
-            reinterpret_cast<const LChar*>(tag_name_stdstr.data()),
-            tag_name_stdstr.size());
+        AtomicString tag_name(base::as_byte_span(tag_name_stdstr));
         if (!tag_name.Impl()->IsStatic()) {
           // `tag_name` should only contain one of the known HTML tags.
           return false;
diff --git a/third_party/blink/renderer/core/streams/miscellaneous_operations.cc b/third_party/blink/renderer/core/streams/miscellaneous_operations.cc
index 985cc0a..d49d491 100644
--- a/third_party/blink/renderer/core/streams/miscellaneous_operations.cc
+++ b/third_party/blink/renderer/core/streams/miscellaneous_operations.cc
@@ -57,9 +57,7 @@
 
 class DefaultSizeAlgorithm final : public StrategySizeAlgorithm {
  public:
-  std::optional<double> Run(ScriptState*,
-                            v8::Local<v8::Value>,
-                            ExceptionState&) override {
+  std::optional<double> Run(ScriptState*, v8::Local<v8::Value>) override {
     return 1;
   }
 };
@@ -70,11 +68,9 @@
       : function_(isolate, size) {}
 
   std::optional<double> Run(ScriptState* script_state,
-                            v8::Local<v8::Value> chunk,
-                            ExceptionState& exception_state) override {
+                            v8::Local<v8::Value> chunk) override {
     auto* isolate = script_state->GetIsolate();
     auto context = script_state->GetContext();
-    v8::TryCatch trycatch(isolate);
     v8::Local<v8::Value> argv[] = {chunk};
 
     // https://streams.spec.whatwg.org/#make-size-algorithm-from-size-function
@@ -83,7 +79,6 @@
         function_.Get(isolate)->Call(context, v8::Undefined(isolate), 1, argv);
     v8::Local<v8::Value> result;
     if (!result_maybe.ToLocal(&result)) {
-      exception_state.RethrowV8Exception(trycatch.Exception());
       return std::nullopt;
     }
 
@@ -93,7 +88,6 @@
     v8::MaybeLocal<v8::Number> number_maybe = result->ToNumber(context);
     v8::Local<v8::Number> number;
     if (!number_maybe.ToLocal(&number)) {
-      exception_state.RethrowV8Exception(trycatch.Exception());
       return std::nullopt;
     }
     return number->Value();
diff --git a/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc b/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc
index ebb6ac1..48dbaf1 100644
--- a/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc
+++ b/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc
@@ -421,9 +421,8 @@
       scope.GetScriptState(), v8::Undefined(scope.GetIsolate()),
       ASSERT_NO_EXCEPTION);
   ASSERT_TRUE(algo);
-  auto optional =
-      algo->Run(scope.GetScriptState(), v8::Number::New(scope.GetIsolate(), 97),
-                ASSERT_NO_EXCEPTION);
+  auto optional = algo->Run(scope.GetScriptState(),
+                            v8::Number::New(scope.GetIsolate(), 97));
   ASSERT_TRUE(optional.has_value());
   EXPECT_EQ(optional.value(), 1.0);
 }
@@ -453,9 +452,8 @@
   V8TestingScope scope;
   auto* algo = IdentitySizeAlgorithm(&scope);
   ASSERT_TRUE(algo);
-  auto optional =
-      algo->Run(scope.GetScriptState(), v8::Number::New(scope.GetIsolate(), 41),
-                ASSERT_NO_EXCEPTION);
+  auto optional = algo->Run(scope.GetScriptState(),
+                            v8::Number::New(scope.GetIsolate(), 41));
   ASSERT_TRUE(optional.has_value());
   EXPECT_EQ(optional.value(), 41.0);
 }
@@ -466,8 +464,7 @@
   auto* algo = IdentitySizeAlgorithm(&scope);
   ASSERT_TRUE(algo);
   auto optional =
-      algo->Run(scope.GetScriptState(), V8String(scope.GetIsolate(), "79"),
-                ASSERT_NO_EXCEPTION);
+      algo->Run(scope.GetScriptState(), V8String(scope.GetIsolate(), "79"));
   ASSERT_TRUE(optional.has_value());
   EXPECT_EQ(optional.value(), 79.0);
 }
@@ -481,14 +478,12 @@
   auto* algo = MakeSizeAlgorithmFromSizeFunction(
       scope.GetScriptState(), function_value.V8Value(), ASSERT_NO_EXCEPTION);
   ASSERT_TRUE(algo);
-  ExceptionState exception_state(scope.GetIsolate(),
-                                 v8::ExceptionContext::kOperation, "", "");
+  v8::TryCatch try_catch(scope.GetIsolate());
   auto optional =
-      algo->Run(scope.GetScriptState(), V8String(scope.GetIsolate(), "79"),
-                exception_state);
+      algo->Run(scope.GetScriptState(), V8String(scope.GetIsolate(), "79"));
 
   ASSERT_FALSE(optional.has_value());
-  EXPECT_TRUE(exception_state.HadException());
+  EXPECT_TRUE(try_catch.HasCaught());
 }
 
 TEST(MiscellaneousOperationsTest, UnconvertibleSize) {
@@ -499,13 +494,11 @@
   ScriptValue unconvertible_value =
       EvalWithPrintingError(&scope, "({ toString() { throw new Error(); }})");
   EXPECT_TRUE(unconvertible_value.IsObject());
-  ExceptionState exception_state(scope.GetIsolate(),
-                                 v8::ExceptionContext::kOperation, "", "");
-  auto optional = algo->Run(scope.GetScriptState(),
-                            unconvertible_value.V8Value(), exception_state);
+  v8::TryCatch try_catch(scope.GetIsolate());
+  auto optional =
+      algo->Run(scope.GetScriptState(), unconvertible_value.V8Value());
 
   ASSERT_FALSE(optional.has_value());
-  EXPECT_TRUE(exception_state.HadException());
 }
 
 TEST(MiscellaneousOperationsTest, PromiseResolve) {
diff --git a/third_party/blink/renderer/core/streams/readable_stream_default_controller.cc b/third_party/blink/renderer/core/streams/readable_stream_default_controller.cc
index 19dd73b..cee9c64 100644
--- a/third_party/blink/renderer/core/streams/readable_stream_default_controller.cc
+++ b/third_party/blink/renderer/core/streams/readable_stream_default_controller.cc
@@ -207,15 +207,15 @@
     //   a. Let result be the result of performing controller.
     //      [[strategySizeAlgorithm]], passing in chunk, and interpreting the
     //      result as an ECMAScript completion value.
+    TryRethrowScope rethrow_scope(script_state->GetIsolate(), exception_state);
     std::optional<double> chunk_size =
-        controller->strategy_size_algorithm_->Run(script_state, chunk,
-                                                  exception_state);
+        controller->strategy_size_algorithm_->Run(script_state, chunk);
 
     //   b. If result is an abrupt completion,
-    if (exception_state.HadException()) {
+    if (rethrow_scope.HasCaught()) {
       //    i. Perform ! ReadableStreamDefaultControllerError(controller,
       //       result.[[Value]]).
-      Error(script_state, controller, exception_state.GetException());
+      Error(script_state, controller, rethrow_scope.GetException());
       //    ii. Return result.
       return;
     }
@@ -225,13 +225,14 @@
     //  d. Let enqueueResult be EnqueueValueWithSize(controller, chunk,
     //     chunkSize).
     controller->queue_->EnqueueValueWithSize(
-        script_state->GetIsolate(), chunk, chunk_size.value(), exception_state);
+        script_state->GetIsolate(), chunk, chunk_size.value(),
+        PassThroughException(script_state->GetIsolate()));
 
     //   e. If enqueueResult is an abrupt completion,
-    if (exception_state.HadException()) {
+    if (rethrow_scope.HasCaught()) {
       //    i. Perform ! ReadableStreamDefaultControllerError(controller,
       //       enqueueResult.[[Value]]).
-      Error(script_state, controller, exception_state.GetException());
+      Error(script_state, controller, rethrow_scope.GetException());
       //    ii. Return enqueueResult.
       return;
     }
diff --git a/third_party/blink/renderer/core/streams/stream_algorithms.h b/third_party/blink/renderer/core/streams/stream_algorithms.h
index 363a735..dacfb58 100644
--- a/third_party/blink/renderer/core/streams/stream_algorithms.h
+++ b/third_party/blink/renderer/core/streams/stream_algorithms.h
@@ -27,8 +27,7 @@
   virtual ~StrategySizeAlgorithm() = default;
 
   virtual std::optional<double> Run(ScriptState*,
-                                    v8::Local<v8::Value> chunk,
-                                    ExceptionState&) = 0;
+                                    v8::Local<v8::Value> chunk) = 0;
 
   virtual void Trace(Visitor*) const {}
 };
diff --git a/third_party/blink/renderer/core/streams/transform_stream_default_controller.cc b/third_party/blink/renderer/core/streams/transform_stream_default_controller.cc
index 771e982..5675ff1 100644
--- a/third_party/blink/renderer/core/streams/transform_stream_default_controller.cc
+++ b/third_party/blink/renderer/core/streams/transform_stream_default_controller.cc
@@ -305,21 +305,21 @@
 
   // 4. Let enqueueResult be ReadableStreamDefaultControllerEnqueue(
   //    readableController, chunk).
-  v8::TryCatch try_catch(script_state->GetIsolate());
+  v8::Isolate* isolate = script_state->GetIsolate();
+  TryRethrowScope rethrow_scope(isolate, exception_state);
   ReadableStreamDefaultController::Enqueue(
-      script_state, readable_controller, chunk,
-      PassThroughException(script_state->GetIsolate()));
+      script_state, readable_controller, chunk, PassThroughException(isolate));
 
   // 5. If enqueueResult is an abrupt completion,
-  if (try_catch.HasCaught()) {
+  if (rethrow_scope.HasCaught()) {
     // a. Perform ! TransformStreamErrorWritableAndUnblockWrite(stream,
     //    enqueueResult.[[Value]]).
     TransformStream::ErrorWritableAndUnblockWrite(script_state, stream,
-                                                  try_catch.Exception());
+                                                  rethrow_scope.GetException());
 
     // b. Throw stream.[[readable]].[[storedError]].
-    exception_state.RethrowV8Exception(
-        stream->readable_->GetStoredError(script_state->GetIsolate()));
+    V8ThrowException::ThrowException(
+        isolate, stream->readable_->GetStoredError(isolate));
     return;
   }
 
diff --git a/third_party/blink/renderer/core/streams/writable_stream_default_controller.cc b/third_party/blink/renderer/core/streams/writable_stream_default_controller.cc
index ed3e0a784..2a15d05 100644
--- a/third_party/blink/renderer/core/streams/writable_stream_default_controller.cc
+++ b/third_party/blink/renderer/core/streams/writable_stream_default_controller.cc
@@ -407,8 +407,8 @@
   //     controller.[[strategySizeAlgorithm]], passing in chunk, and
   //     interpreting the result as an ECMAScript completion value.
   v8::TryCatch try_catch(script_state->GetIsolate());
-  auto return_value = controller->strategy_size_algorithm_->Run(
-      script_state, chunk, PassThroughException(script_state->GetIsolate()));
+  auto return_value =
+      controller->strategy_size_algorithm_->Run(script_state, chunk);
 
   //  2. If returnValue is an abrupt completion,
   if (!return_value.has_value()) {
diff --git a/third_party/blink/renderer/core/testing/callback_function_test.cc b/third_party/blink/renderer/core/testing/callback_function_test.cc
index c5612fa7..906aee8 100644
--- a/third_party/blink/renderer/core/testing/callback_function_test.cc
+++ b/third_party/blink/renderer/core/testing/callback_function_test.cc
@@ -71,10 +71,10 @@
 }
 
 void CallbackFunctionTest::testEnumCallback(V8TestEnumCallback* callback,
-                                            const String& enum_value,
+                                            const V8InternalEnum& enum_value,
                                             ExceptionState& exception_state) {
   callback->InvokeAndReportException(
-      nullptr, V8InternalEnum::Create(enum_value).value());
+      nullptr, V8InternalEnum::Create((String)enum_value).value());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/testing/callback_function_test.h b/third_party/blink/renderer/core/testing/callback_function_test.h
index 6a87e4c..0573de70 100644
--- a/third_party/blink/renderer/core/testing/callback_function_test.h
+++ b/third_party/blink/renderer/core/testing/callback_function_test.h
@@ -13,6 +13,7 @@
 
 class ExceptionState;
 class HTMLDivElement;
+class V8InternalEnum;
 class V8TestCallback;
 class V8TestEnumCallback;
 class V8TestInterfaceCallback;
@@ -40,7 +41,7 @@
                                       const Vector<int>& numbers,
                                       ExceptionState&);
   void testEnumCallback(V8TestEnumCallback*,
-                        const String& enum_value,
+                        const V8InternalEnum& enum_value,
                         ExceptionState&);
 };
 
diff --git a/third_party/blink/renderer/modules/eventsource/event_source.cc b/third_party/blink/renderer/modules/eventsource/event_source.cc
index 9727d5c..6e00f4ba 100644
--- a/third_party/blink/renderer/modules/eventsource/event_source.cc
+++ b/third_party/blink/renderer/modules/eventsource/event_source.cc
@@ -170,8 +170,7 @@
     std::string last_event_id_utf8 = parser_->LastEventId().Utf8();
     request.SetHttpHeaderField(
         http_names::kLastEventID,
-        AtomicString(reinterpret_cast<const LChar*>(last_event_id_utf8.c_str()),
-                     last_event_id_utf8.length()));
+        AtomicString(base::as_byte_span(last_event_id_utf8)));
   }
 
   ResourceLoaderOptions resource_loader_options(world_);
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder.cc b/third_party/blink/renderer/modules/mediarecorder/media_recorder.cc
index fa6502f..2f6fc77 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder.cc
@@ -380,9 +380,8 @@
         "The MediaRecorder's state is '" + StateToString(state_) + "'.");
     return;
   }
-  WriteData({}, /*last_in_slice=*/true,
-            base::Time::Now().InMillisecondsFSinceUnixEpoch(),
-            /*error_event=*/nullptr);
+
+  WriteData(/*data=*/{}, /*last_in_slice=*/true, /*error_event=*/nullptr);
 }
 
 bool MediaRecorder::isTypeSupported(ExecutionContext* context,
@@ -427,9 +426,8 @@
   if (blob_data_) {
     // Cache |blob_data_->length()| because of std::move in argument list.
     const uint64_t blob_data_length = blob_data_->length();
-    CreateBlobEvent(MakeGarbageCollected<Blob>(BlobDataHandle::Create(
-                        std::move(blob_data_), blob_data_length)),
-                    base::Time::Now().InMillisecondsFSinceUnixEpoch());
+    CreateBlobEvent(MakeGarbageCollected<Blob>(
+        BlobDataHandle::Create(std::move(blob_data_), blob_data_length)));
   }
 
   state_ = State::kInactive;
@@ -442,7 +440,6 @@
 
 void MediaRecorder::WriteData(base::span<const uint8_t> data,
                               bool last_in_slice,
-                              double timecode,
                               ErrorEvent* error_event) {
   if (!first_write_received_) {
     mime_type_ = recorder_handler_->ActualMimeType();
@@ -461,14 +458,14 @@
   if (!data.empty()) {
     blob_data_->AppendBytes(data);
   }
+
   if (!last_in_slice)
     return;
 
   // Cache |blob_data_->length()| because of std::move in argument list.
   const uint64_t blob_data_length = blob_data_->length();
-  CreateBlobEvent(MakeGarbageCollected<Blob>(BlobDataHandle::Create(
-                      std::move(blob_data_), blob_data_length)),
-                  timecode);
+  CreateBlobEvent(MakeGarbageCollected<Blob>(
+      BlobDataHandle::Create(std::move(blob_data_), blob_data_length)));
 }
 
 void MediaRecorder::OnError(DOMExceptionCode code, const String& message) {
@@ -498,7 +495,16 @@
   }
 }
 
-void MediaRecorder::CreateBlobEvent(Blob* blob, double timecode) {
+void MediaRecorder::CreateBlobEvent(Blob* blob) {
+  const base::TimeTicks now = base::TimeTicks::Now();
+  double timecode = 0;
+  if (!blob_event_first_chunk_timecode_.has_value()) {
+    blob_event_first_chunk_timecode_ = now;
+  } else {
+    timecode =
+        (now - blob_event_first_chunk_timecode_.value()).InMillisecondsF();
+  }
+
   ScheduleDispatchEvent(MakeGarbageCollected<BlobEvent>(
       event_type_names::kDataavailable, blob, timecode));
 }
@@ -519,8 +525,7 @@
   state_ = State::kInactive;
 
   recorder_handler_->Stop();
-  WriteData({}, /*last_in_slice=*/true,
-            base::Time::Now().InMillisecondsFSinceUnixEpoch(), error_event);
+  WriteData(/*data=*/{}, /*last_in_slice=*/true, error_event);
   ScheduleDispatchEvent(Event::Create(event_type_names::kStop));
   first_write_received_ = false;
 }
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder.h b/third_party/blink/renderer/modules/mediarecorder/media_recorder.h
index e64e73b..3cfc82e 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder.h
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder.h
@@ -80,7 +80,6 @@
 
   virtual void WriteData(base::span<const uint8_t> data,
                          bool last_in_slice,
-                         double timecode,
                          ErrorEvent* error_event);
   virtual void OnError(DOMExceptionCode code, const String& message);
 
@@ -97,7 +96,7 @@
   void UpdateAudioBitrate(uint32_t bits_per_second);
 
  private:
-  void CreateBlobEvent(Blob* blob, double timecode);
+  void CreateBlobEvent(Blob* blob);
 
   void StopRecording(ErrorEvent* error_event);
   void ScheduleDispatchEvent(Event* event);
@@ -112,6 +111,7 @@
   State state_ = State::kInactive;
   bool first_write_received_ = false;
   std::unique_ptr<BlobData> blob_data_;
+  std::optional<base::TimeTicks> blob_event_first_chunk_timecode_;
   Member<MediaRecorderHandler> recorder_handler_;
   HeapVector<Member<Event>> scheduled_events_;
 };
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
index f60e93c..d24c2c2 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
@@ -804,7 +804,7 @@
     base::TimeTicks timestamp) {
   DCHECK(IsMainThread());
 
-  if (encoded_data->empty() && encoded_data->side_data()->alpha_data.empty()) {
+  if (encoded_data->empty()) {
     // An encoder drops a frame. This can happen with VideoToolBox encoder as
     // there is no way to disallow the frame dropping with it.
     return;
@@ -944,21 +944,13 @@
   DVLOG(3) << __func__ << " " << data.size() << "B";
 
   const base::TimeTicks now = base::TimeTicks::Now();
-  // Non-buffered mode does not need to check timestamps.
-  if (timeslice_.is_zero()) {
-    recorder_->WriteData(data, /*last_in_slice=*/true,
-                         (now - base::TimeTicks::UnixEpoch()).InMillisecondsF(),
-                         /*error_event=*/nullptr);
-    return;
-  }
-
-  const bool last_in_slice = now > slice_origin_timestamp_ + timeslice_;
+  const bool last_in_slice =
+      timeslice_.is_zero() ? true : now > slice_origin_timestamp_ + timeslice_;
   DVLOG_IF(1, last_in_slice) << "Slice finished @ " << now;
-  if (last_in_slice)
+  if (last_in_slice) {
     slice_origin_timestamp_ = now;
-  recorder_->WriteData(base::as_byte_span(data), last_in_slice,
-                       (now - base::TimeTicks::UnixEpoch()).InMillisecondsF(),
-                       /*error_event=*/nullptr);
+  }
+  recorder_->WriteData(data, last_in_slice, /*error_event=*/nullptr);
 }
 
 void MediaRecorderHandler::UpdateTracksLiveAndEnabled() {
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler_unittest.cc b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler_unittest.cc
index 42eae94..3173bdb 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler_unittest.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler_unittest.cc
@@ -164,9 +164,7 @@
                       scope.GetExceptionState()) {}
   ~MockMediaRecorder() override = default;
 
-  MOCK_METHOD(void,
-              WriteData,
-              (base::span<const uint8_t>, bool, double, ErrorEvent*));
+  MOCK_METHOD(void, WriteData, (base::span<const uint8_t>, bool, ErrorEvent*));
   MOCK_METHOD(void, OnError, (DOMExceptionCode code, const String& message));
 };
 
@@ -596,11 +594,11 @@
       base::RunLoop run_loop;
       // WriteData is called as many as fragments (`moof` box) in addition
       // to 3 times of `ftyp`, `moov`, `mfra` boxes.
-      EXPECT_CALL(*recorder, WriteData(SizeIs(Lt(kMfraBoxSize)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Lt(kMfraBoxSize)), _, _))
           .Times(AtLeast(1));
-      EXPECT_CALL(*recorder, WriteData(SizeIs(Gt(kMfraBoxSize)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Gt(kMfraBoxSize)), _, _))
           .Times(AtLeast(1));
-      EXPECT_CALL(*recorder, WriteData(SizeIs(kMfraBoxSize), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(kMfraBoxSize), _, _))
           .Times(1)
           .WillOnce(RunOnceClosure(run_loop.QuitClosure()));
 
@@ -617,11 +615,9 @@
       base::RunLoop run_loop;
       // writeData() is pinged a number of times as the WebM header is written;
       // the last time it is called it has the encoded data.
-      EXPECT_CALL(*recorder,
-                  WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _))
           .Times(AtLeast(1));
-      EXPECT_CALL(*recorder,
-                  WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _))
           .Times(1)
           .WillOnce(RunOnceClosure(run_loop.QuitClosure()));
 
@@ -634,11 +630,9 @@
       base::RunLoop run_loop;
       // The second time around writeData() is called a number of times to write
       // the WebM frame header, and then is pinged with the encoded data.
-      EXPECT_CALL(*recorder,
-                  WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _))
           .Times(AtLeast(1));
-      EXPECT_CALL(*recorder,
-                  WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _))
           .Times(1)
           .WillOnce(RunOnceClosure(run_loop.QuitClosure()));
 
@@ -654,19 +648,17 @@
       base::RunLoop run_loop;
       // The second time around writeData() is called a number of times to write
       // the WebM frame header, and then is pinged with the encoded data.
-      EXPECT_CALL(*recorder,
-                  WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _))
           .Times(AtLeast(1));
-      EXPECT_CALL(*recorder,
-                  WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _))
           .Times(1)
           .WillOnce(RunOnceClosure(run_loop.QuitClosure()));
       if (GetParam().encoder_supports_alpha) {
         EXPECT_CALL(*recorder,
-                    WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _, _))
+                    WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _))
             .Times(AtLeast(1));
         EXPECT_CALL(*recorder,
-                    WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _, _))
+                    WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _))
             .Times(1)
             .WillOnce(RunOnceClosure(run_loop.QuitClosure()));
       }
@@ -715,11 +707,9 @@
     base::RunLoop run_loop;
     // WriteData is called as many as fragments (`moof` box) in addition
     // to 2 times of `ftyp`, `moov` boxes (no 'mfra'box as it is audio only).
-    EXPECT_CALL(*recorder,
-                WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _, _))
+    EXPECT_CALL(*recorder, WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _))
         .Times(AtLeast(1));
-    EXPECT_CALL(*recorder,
-                WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _, _))
+    EXPECT_CALL(*recorder, WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _))
         .Times(2)
         .WillOnce(RunOnceClosure(run_loop.QuitClosure()));
 
@@ -744,11 +734,9 @@
       base::RunLoop run_loop;
       // writeData() is pinged a number of times as the WebM header is written;
       // the last time it is called it has the encoded data.
-      EXPECT_CALL(*recorder,
-                  WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _))
           .Times(AtLeast(1));
-      EXPECT_CALL(*recorder,
-                  WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _))
           .Times(1)
           .WillOnce(RunOnceClosure(run_loop.QuitClosure()));
 
@@ -763,11 +751,9 @@
       base::RunLoop run_loop;
       // The second time around writeData() is called a number of times to write
       // the WebM frame header, and then is pinged with the encoded data.
-      EXPECT_CALL(*recorder,
-                  WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Lt(kEncodedSizeThreshold)), _, _))
           .Times(AtLeast(1));
-      EXPECT_CALL(*recorder,
-                  WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _, _))
+      EXPECT_CALL(*recorder, WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _))
           .Times(1)
           .WillOnce(RunOnceClosure(run_loop.QuitClosure()));
 
@@ -810,8 +796,7 @@
     const size_t kEncodedSizeThreshold = 16;
     base::RunLoop run_loop;
     EXPECT_CALL(*recorder, WriteData).Times(AtLeast(1));
-    EXPECT_CALL(*recorder,
-                WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _, _))
+    EXPECT_CALL(*recorder, WriteData(SizeIs(Gt(kEncodedSizeThreshold)), _, _))
         .Times(1)
         .WillOnce(RunOnceClosure(run_loop.QuitClosure()));
 
@@ -1656,7 +1641,7 @@
   {
     base::RunLoop run_loop;
     EXPECT_CALL(*recorder, WriteData).Times(AtLeast(1));
-    EXPECT_CALL(*recorder, WriteData(SizeIs(Ge(kFrameSize)), _, _, _))
+    EXPECT_CALL(*recorder, WriteData(SizeIs(Ge(kFrameSize)), _, _))
         .Times(1)
         .WillOnce(RunOnceClosure(run_loop.QuitClosure()));
     OnVideoFrameForTesting(frame);
diff --git a/third_party/blink/renderer/modules/payments/payment_response.cc b/third_party/blink/renderer/modules/payments/payment_response.cc
index c11ad8a..772848b9 100644
--- a/third_party/blink/renderer/modules/payments/payment_response.cc
+++ b/third_party/blink/renderer/modules/payments/payment_response.cc
@@ -9,6 +9,7 @@
 #include "base/logging.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_payment_complete.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_validation_errors.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/modules/credentialmanagement/authenticator_assertion_response.h"
@@ -145,16 +146,23 @@
 
 ScriptPromise<IDLUndefined> PaymentResponse::complete(
     ScriptState* script_state,
-    const String& result,
+    const V8PaymentComplete& result,
     ExceptionState& exception_state) {
   VLOG(2) << "Renderer: PaymentRequest (" << requestId().Utf8()
-          << "): complete(" << result << ")";
+          << "): complete(" << String(result) << ")";
   PaymentStateResolver::PaymentComplete converted_result =
       PaymentStateResolver::PaymentComplete::kUnknown;
-  if (result == "success")
-    converted_result = PaymentStateResolver::PaymentComplete::kSuccess;
-  else if (result == "fail")
-    converted_result = PaymentStateResolver::PaymentComplete::kFail;
+  switch (result.AsEnum()) {
+    case V8PaymentComplete::Enum::kUnknown:
+      converted_result = PaymentStateResolver::PaymentComplete::kUnknown;
+      break;
+    case V8PaymentComplete::Enum::kSuccess:
+      converted_result = PaymentStateResolver::PaymentComplete::kSuccess;
+      break;
+    case V8PaymentComplete::Enum::kFail:
+      converted_result = PaymentStateResolver::PaymentComplete::kFail;
+      break;
+  }
   return payment_state_resolver_->Complete(script_state, converted_result,
                                            exception_state);
 }
diff --git a/third_party/blink/renderer/modules/payments/payment_response.h b/third_party/blink/renderer/modules/payments/payment_response.h
index 38375374..cd366ec6 100644
--- a/third_party/blink/renderer/modules/payments/payment_response.h
+++ b/third_party/blink/renderer/modules/payments/payment_response.h
@@ -25,6 +25,7 @@
 class PaymentStateResolver;
 class PaymentValidationErrors;
 class ScriptState;
+class V8PaymentComplete;
 
 class MODULES_EXPORT PaymentResponse final
     : public EventTarget,
@@ -61,7 +62,7 @@
   const String& payerPhone() const { return payer_phone_; }
 
   ScriptPromise<IDLUndefined> complete(ScriptState*,
-                                       const String& result,
+                                       const V8PaymentComplete& result,
                                        ExceptionState&);
   ScriptPromise<IDLUndefined> retry(ScriptState*,
                                     const PaymentValidationErrors*,
diff --git a/third_party/blink/renderer/modules/payments/payment_response_test.cc b/third_party/blink/renderer/modules/payments/payment_response_test.cc
index a1bf00dd2..33c9a3c 100644
--- a/third_party/blink/renderer/modules/payments/payment_response_test.cc
+++ b/third_party/blink/renderer/modules/payments/payment_response_test.cc
@@ -18,6 +18,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_payment_complete.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_validation_errors.h"
 #include "third_party/blink/renderer/modules/credentialmanagement/public_key_credential.h"
 #include "third_party/blink/renderer/modules/payments/payment_address.h"
@@ -242,7 +243,8 @@
               Complete(scope.GetScriptState(), PaymentStateResolver::kSuccess,
                        testing::_));
 
-  output->complete(scope.GetScriptState(), "success",
+  output->complete(scope.GetScriptState(),
+                   V8PaymentComplete(V8PaymentComplete::Enum::kSuccess),
                    scope.GetExceptionState());
 }
 
@@ -263,7 +265,9 @@
               Complete(scope.GetScriptState(), PaymentStateResolver::kFail,
                        testing::_));
 
-  output->complete(scope.GetScriptState(), "fail", scope.GetExceptionState());
+  output->complete(scope.GetScriptState(),
+                   V8PaymentComplete(V8PaymentComplete::Enum::kFail),
+                   scope.GetExceptionState());
 }
 
 TEST(PaymentResponseTest, JSONSerializerTest) {
diff --git a/third_party/blink/renderer/modules/presentation/presentation_connection.cc b/third_party/blink/renderer/modules/presentation/presentation_connection.cc
index 1844302..fd02836 100644
--- a/third_party/blink/renderer/modules/presentation/presentation_connection.cc
+++ b/third_party/blink/renderer/modules/presentation/presentation_connection.cc
@@ -162,7 +162,6 @@
       state_(mojom::blink::PresentationConnectionState::CONNECTING),
       connection_receiver_(this, &window),
       target_connection_(&window),
-      binary_type_(kBinaryTypeArrayBuffer),
       file_reading_task_runner_(window.GetTaskRunner(TaskType::kFileReading)) {
   UpdateStateIfNeeded();
 }
@@ -540,27 +539,12 @@
   }
 }
 
-String PresentationConnection::binaryType() const {
-  switch (binary_type_) {
-    case kBinaryTypeBlob:
-      return "blob";
-    case kBinaryTypeArrayBuffer:
-      return "arraybuffer";
-  }
-  NOTREACHED_IN_MIGRATION();
-  return String();
+V8BinaryType PresentationConnection::binaryType() const {
+  return V8BinaryType(binary_type_);
 }
 
-void PresentationConnection::setBinaryType(const String& binary_type) {
-  if (binary_type == "blob") {
-    binary_type_ = kBinaryTypeBlob;
-    return;
-  }
-  if (binary_type == "arraybuffer") {
-    binary_type_ = kBinaryTypeArrayBuffer;
-    return;
-  }
-  NOTREACHED_IN_MIGRATION();
+void PresentationConnection::setBinaryType(const V8BinaryType& binary_type) {
+  binary_type_ = binary_type.AsEnum();
 }
 
 void PresentationConnection::SendMessageToTargetConnection(
@@ -582,7 +566,7 @@
     return;
 
   switch (binary_type_) {
-    case kBinaryTypeBlob: {
+    case V8BinaryType::Enum::kBlob: {
       auto blob_data = std::make_unique<BlobData>();
       blob_data->AppendBytes(data);
       auto* blob = MakeGarbageCollected<Blob>(
@@ -590,7 +574,7 @@
       DispatchEvent(*MessageEvent::Create(blob));
       return;
     }
-    case kBinaryTypeArrayBuffer:
+    case V8BinaryType::Enum::kArraybuffer:
       DOMArrayBuffer* buffer = DOMArrayBuffer::Create(data);
       DispatchEvent(*MessageEvent::Create(buffer));
       return;
diff --git a/third_party/blink/renderer/modules/presentation/presentation_connection.h b/third_party/blink/renderer/modules/presentation/presentation_connection.h
index 71a4467..fe0ac2a 100644
--- a/third_party/blink/renderer/modules/presentation/presentation_connection.h
+++ b/third_party/blink/renderer/modules/presentation/presentation_connection.h
@@ -9,6 +9,7 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/mojom/presentation/presentation.mojom-blink.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_binary_type.h"
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_state_observer.h"
 #include "third_party/blink/renderer/core/fileapi/blob.h"
@@ -65,8 +66,8 @@
   // connected to.
   void terminate();
 
-  String binaryType() const;
-  void setBinaryType(const String&);
+  V8BinaryType binaryType() const;
+  void setBinaryType(const V8BinaryType&);
 
   DEFINE_ATTRIBUTE_EVENT_LISTENER(message, kMessage)
   DEFINE_ATTRIBUTE_EVENT_LISTENER(connect, kConnect)
@@ -125,8 +126,6 @@
     kMessageTypeBlob,
   };
 
-  enum BinaryType { kBinaryTypeBlob, kBinaryTypeArrayBuffer };
-
   class Message;
 
   // Implemented by controller/receiver subclasses to perform additional
@@ -157,7 +156,7 @@
   Member<BlobLoader> blob_loader_;
   HeapDeque<Member<Message>> messages_;
 
-  BinaryType binary_type_;
+  V8BinaryType::Enum binary_type_ = V8BinaryType::Enum::kArraybuffer;
 
   scoped_refptr<base::SingleThreadTaskRunner> file_reading_task_runner_;
 };
diff --git a/third_party/blink/renderer/modules/webcodecs/decoder_template.h b/third_party/blink/renderer/modules/webcodecs/decoder_template.h
index f655c58..3f93de7 100644
--- a/third_party/blink/renderer/modules/webcodecs/decoder_template.h
+++ b/third_party/blink/renderer/modules/webcodecs/decoder_template.h
@@ -65,7 +65,7 @@
   ScriptPromise<IDLUndefined> flush(ExceptionState&);
   void reset(ExceptionState&);
   void close(ExceptionState&);
-  String state() const { return state_; }
+  V8CodecState state() const { return state_; }
 
   // EventTarget override.
   ExecutionContext* GetExecutionContext() const override;
diff --git a/third_party/blink/renderer/modules/webcodecs/encoder_base.h b/third_party/blink/renderer/modules/webcodecs/encoder_base.h
index d4e96a1..3854e33 100644
--- a/third_party/blink/renderer/modules/webcodecs/encoder_base.h
+++ b/third_party/blink/renderer/modules/webcodecs/encoder_base.h
@@ -69,7 +69,7 @@
 
   void close(ExceptionState&);
 
-  String state() { return state_; }
+  V8CodecState state() { return state_; }
 
   // EventTarget override.
   ExecutionContext* GetExecutionContext() const override;
diff --git a/third_party/blink/renderer/modules/websockets/dom_websocket.cc b/third_party/blink/renderer/modules/websockets/dom_websocket.cc
index 5be6a96..7434bd3a 100644
--- a/third_party/blink/renderer/modules/websockets/dom_websocket.cc
+++ b/third_party/blink/renderer/modules/websockets/dom_websocket.cc
@@ -176,7 +176,6 @@
       buffered_amount_(0),
       consumed_buffered_amount_(0),
       buffered_amount_after_close_(0),
-      binary_type_(kBinaryTypeBlob),
       subprotocol_(""),
       extensions_(""),
       event_queue_(MakeGarbageCollected<EventQueue>(this)),
@@ -448,27 +447,12 @@
   return extensions_;
 }
 
-String DOMWebSocket::binaryType() const {
-  switch (binary_type_) {
-    case kBinaryTypeBlob:
-      return "blob";
-    case kBinaryTypeArrayBuffer:
-      return "arraybuffer";
-  }
-  NOTREACHED_IN_MIGRATION();
-  return String();
+V8BinaryType DOMWebSocket::binaryType() const {
+  return V8BinaryType(binary_type_);
 }
 
-void DOMWebSocket::setBinaryType(const String& binary_type) {
-  if (binary_type == "blob") {
-    binary_type_ = kBinaryTypeBlob;
-    return;
-  }
-  if (binary_type == "arraybuffer") {
-    binary_type_ = kBinaryTypeArrayBuffer;
-    return;
-  }
-  NOTREACHED_IN_MIGRATION();
+void DOMWebSocket::setBinaryType(const V8BinaryType& binary_type) {
+  binary_type_ = binary_type.AsEnum();
 }
 
 const AtomicString& DOMWebSocket::InterfaceName() const {
@@ -549,7 +533,7 @@
     return;
 
   switch (binary_type_) {
-    case kBinaryTypeBlob: {
+    case V8BinaryType::Enum::kBlob: {
       auto blob_data = std::make_unique<BlobData>();
       for (const auto& span : data) {
         blob_data->AppendBytes(base::as_bytes(span));
@@ -560,7 +544,7 @@
       break;
     }
 
-    case kBinaryTypeArrayBuffer:
+    case V8BinaryType::Enum::kArraybuffer:
       DOMArrayBuffer* array_buffer = DOMArrayBuffer::Create(data);
       event_queue_->Dispatch(
           MessageEvent::Create(array_buffer, origin_string_));
diff --git a/third_party/blink/renderer/modules/websockets/dom_websocket.h b/third_party/blink/renderer/modules/websockets/dom_websocket.h
index e2bce47..f5371a7 100644
--- a/third_party/blink/renderer/modules/websockets/dom_websocket.h
+++ b/third_party/blink/renderer/modules/websockets/dom_websocket.h
@@ -37,6 +37,7 @@
 
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/bindings/core/v8/capture_source_location.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_binary_type.h"
 #include "third_party/blink/renderer/core/dom/events/event_listener.h"
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_state_observer.h"
@@ -117,8 +118,8 @@
   String protocol() const;
   String extensions() const;
 
-  String binaryType() const;
-  void setBinaryType(const String&);
+  V8BinaryType binaryType() const;
+  void setBinaryType(const V8BinaryType&);
 
   DEFINE_ATTRIBUTE_EVENT_LISTENER(open, kOpen)
   DEFINE_ATTRIBUTE_EVENT_LISTENER(message, kMessage)
@@ -203,8 +204,6 @@
     kMaxValue = kBlob,
   };
 
-  enum BinaryType { kBinaryTypeBlob, kBinaryTypeArrayBuffer };
-
   // This function is virtual for unittests.
   virtual WebSocketChannel* CreateChannel(ExecutionContext* context,
                                           WebSocketChannelClient* client) {
@@ -254,7 +253,7 @@
   // later. It will be cleared once reflected.
   uint64_t consumed_buffered_amount_;
   uint64_t buffered_amount_after_close_;
-  BinaryType binary_type_;
+  V8BinaryType::Enum binary_type_ = V8BinaryType::Enum::kBlob;
   // The subprotocol the server selected.
   String subprotocol_;
   String extensions_;
diff --git a/third_party/blink/renderer/modules/websockets/dom_websocket_test.cc b/third_party/blink/renderer/modules/websockets/dom_websocket_test.cc
index 199b91f3..432dd7b 100644
--- a/third_party/blink/renderer/modules/websockets/dom_websocket_test.cc
+++ b/third_party/blink/renderer/modules/websockets/dom_websocket_test.cc
@@ -866,13 +866,15 @@
   DOMWebSocketTestScope websocket_scope(scope.GetExecutionContext());
   EXPECT_EQ("blob", websocket_scope.Socket().binaryType());
 
-  websocket_scope.Socket().setBinaryType("arraybuffer");
+  websocket_scope.Socket().setBinaryType(
+      V8BinaryType(V8BinaryType::Enum::kArraybuffer));
 
-  EXPECT_EQ("arraybuffer", websocket_scope.Socket().binaryType());
+  EXPECT_EQ("arraybuffer", websocket_scope.Socket().binaryType().AsString());
 
-  websocket_scope.Socket().setBinaryType("blob");
+  websocket_scope.Socket().setBinaryType(
+      V8BinaryType(V8BinaryType::Enum::kBlob));
 
-  EXPECT_EQ("blob", websocket_scope.Socket().binaryType());
+  EXPECT_EQ("blob", websocket_scope.Socket().binaryType().AsString());
 }
 
 // FIXME: We should add tests for suspend / resume.
diff --git a/third_party/blink/renderer/modules/xr/xr_gpu_projection_layer.h b/third_party/blink/renderer/modules/xr/xr_gpu_projection_layer.h
index 23a7158..88739961 100644
--- a/third_party/blink/renderer/modules/xr/xr_gpu_projection_layer.h
+++ b/third_party/blink/renderer/modules/xr/xr_gpu_projection_layer.h
@@ -19,6 +19,14 @@
                        XRGPULayerTextureSwapChain* depth_stencil_swap_chain_);
   ~XRGPUProjectionLayer() override = default;
 
+  // TODO(crbug.com/359428629): Implement WebGPU layer submission
+  void OnFrameStart(
+      const std::optional<gpu::MailboxHolder>& buffer_mailbox_holder,
+      const std::optional<gpu::MailboxHolder>& camera_image_mailbox_holder)
+      override {}
+  void OnFrameEnd() override {}
+  void OnResize() override {}
+
   XRGPULayerTextureSwapChain* color_swap_chain() {
     return color_swap_chain_.Get();
   }
diff --git a/third_party/blink/renderer/modules/xr/xr_layer.h b/third_party/blink/renderer/modules/xr/xr_layer.h
index a23f5c35..cb045cd 100644
--- a/third_party/blink/renderer/modules/xr/xr_layer.h
+++ b/third_party/blink/renderer/modules/xr/xr_layer.h
@@ -5,6 +5,9 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_LAYER_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_LAYER_H_
 
+#include <optional>
+
+#include "gpu/command_buffer/common/mailbox_holder.h"
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 
 namespace blink {
@@ -20,6 +23,12 @@
 
   XRSession* session() const { return session_.Get(); }
 
+  virtual void OnFrameStart(
+      const std::optional<gpu::MailboxHolder>& buffer_mailbox_holder,
+      const std::optional<gpu::MailboxHolder>& camera_image_mailbox_holder) = 0;
+  virtual void OnFrameEnd() = 0;
+  virtual void OnResize() = 0;
+
   // EventTarget overrides.
   ExecutionContext* GetExecutionContext() const override;
   const AtomicString& InterfaceName() const override;
diff --git a/third_party/blink/renderer/modules/xr/xr_render_state.cc b/third_party/blink/renderer/modules/xr/xr_render_state.cc
index de16f632..7bd0c74c 100644
--- a/third_party/blink/renderer/modules/xr/xr_render_state.cc
+++ b/third_party/blink/renderer/modules/xr/xr_render_state.cc
@@ -55,6 +55,16 @@
   }
 }
 
+XRLayer* XRRenderState::GetFirstLayer() const {
+  if (base_layer_) {
+    return base_layer_.Get();
+  }
+  if (layers_->size()) {
+    return layers_->at(0);
+  }
+  return nullptr;
+}
+
 HTMLCanvasElement* XRRenderState::output_canvas() const {
   if (base_layer_) {
     return base_layer_->output_canvas();
diff --git a/third_party/blink/renderer/modules/xr/xr_render_state.h b/third_party/blink/renderer/modules/xr/xr_render_state.h
index e4efc59..8a4e371 100644
--- a/third_party/blink/renderer/modules/xr/xr_render_state.h
+++ b/third_party/blink/renderer/modules/xr/xr_render_state.h
@@ -36,6 +36,9 @@
   XRWebGLLayer* baseLayer() const { return base_layer_.Get(); }
   const FrozenArray<XRLayer>& layers() const { return *layers_.Get(); }
 
+  // Returns either baseLayer or layers[0], or nullptr if neither is set.
+  XRLayer* GetFirstLayer() const;
+
   HTMLCanvasElement* output_canvas() const;
 
   void Update(const XRRenderStateInit* init);
diff --git a/third_party/blink/renderer/modules/xr/xr_session.cc b/third_party/blink/renderer/modules/xr/xr_session.cc
index 01b246a..b3a9ba7e 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.cc
+++ b/third_party/blink/renderer/modules/xr/xr_session.cc
@@ -623,8 +623,9 @@
       }
     }
 
-    if (views_resized && render_state_->baseLayer()) {
-      render_state_->baseLayer()->OnResize();
+    XRLayer* base_layer = render_state_->GetFirstLayer();
+    if (views_resized && base_layer) {
+      base_layer->OnResize();
     }
   } else {  // Inline
     UpdateInlineView();
@@ -1600,10 +1601,12 @@
 }
 
 void XRSession::MaybeRequestFrame() {
-  bool will_have_base_layer = !!render_state_->baseLayer();
+  bool will_have_base_layer = !!render_state_->GetFirstLayer();
   for (const auto& init : pending_render_state_) {
     if (init->hasBaseLayer()) {
       will_have_base_layer = !!init->baseLayer();
+    } else if (init->hasLayers()) {
+      will_have_base_layer = init->layers()->size() > 0;
     }
   }
 
@@ -1674,7 +1677,7 @@
 void XRSession::ApplyPendingRenderState() {
   DCHECK(!prev_base_layer_);
   if (pending_render_state_.size() > 0) {
-    prev_base_layer_ = render_state_->baseLayer();
+    prev_base_layer_ = render_state_->GetFirstLayer();
     HTMLCanvasElement* prev_ouput_canvas = render_state_->output_canvas();
 
     // Loop through each pending render state and apply it to the active one.
@@ -1685,9 +1688,9 @@
 
     // If this is an inline session and the base layer has changed, give it an
     // opportunity to update it's drawing buffer size.
-    if (!immersive() && render_state_->baseLayer() &&
-        render_state_->baseLayer() != prev_base_layer_) {
-      render_state_->baseLayer()->OnResize();
+    XRLayer* base_layer = render_state_->GetFirstLayer();
+    if (!immersive() && base_layer && base_layer != prev_base_layer_) {
+      base_layer->OnResize();
     }
 
     // If the output canvas changed, remove listeners from the old one and add
@@ -1980,8 +1983,7 @@
 
     // Don't allow frames to be processed if there's no layers attached to the
     // session. That would allow tracking with no associated visuals.
-    XRWebGLLayer* frame_base_layer = render_state_->baseLayer();
-    if (!frame_base_layer) {
+    if (!render_state_->GetFirstLayer()) {
       DVLOG(2) << __func__ << ": frame_base_layer not present";
 
       // If we previously had a frame base layer, we need to still attempt to
@@ -2007,6 +2009,7 @@
       return;
     }
 
+    XRLayer* frame_base_layer = render_state_->GetFirstLayer();
     frame_base_layer->OnFrameStart(output_mailbox_holder,
                                    camera_image_mailbox_holder);
 
@@ -2159,8 +2162,9 @@
   output_width_ = element->OffsetWidth() * devicePixelRatio;
   output_height_ = element->OffsetHeight() * devicePixelRatio;
 
-  if (render_state_->baseLayer()) {
-    render_state_->baseLayer()->OnResize();
+  XRLayer* base_layer = render_state_->GetFirstLayer();
+  if (base_layer) {
+    base_layer->OnResize();
   }
 
   canvas_was_resized_ = true;
diff --git a/third_party/blink/renderer/modules/xr/xr_session.h b/third_party/blink/renderer/modules/xr/xr_session.h
index e4dd689..070b908 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.h
+++ b/third_party/blink/renderer/modules/xr/xr_session.h
@@ -64,7 +64,7 @@
 class XRTransientInputHitTestOptionsInit;
 class XRTransientInputHitTestSource;
 class XRViewData;
-class XRWebGLLayer;
+class XRLayer;
 
 template <typename IDLType>
 class FrozenArray;
@@ -579,7 +579,7 @@
   HeapVector<Member<XRViewData>> views_;
 
   Member<XRInputSourceArray> input_sources_;
-  Member<XRWebGLLayer> prev_base_layer_;
+  Member<XRLayer> prev_base_layer_;
   Member<ResizeObserver> resize_observer_;
   Member<XRCanvasInputProvider> canvas_input_provider_;
   Member<Element> overlay_element_;
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_layer.h b/third_party/blink/renderer/modules/xr/xr_webgl_layer.h
index c12613d9..ccf3ab1 100644
--- a/third_party/blink/renderer/modules/xr/xr_webgl_layer.h
+++ b/third_party/blink/renderer/modules/xr/xr_webgl_layer.h
@@ -74,9 +74,10 @@
 
   void OnFrameStart(
       const std::optional<gpu::MailboxHolder>& buffer_mailbox_holder,
-      const std::optional<gpu::MailboxHolder>& camera_image_mailbox_holder);
-  void OnFrameEnd();
-  void OnResize();
+      const std::optional<gpu::MailboxHolder>& camera_image_mailbox_holder)
+      override;
+  void OnFrameEnd() override;
+  void OnResize() override;
 
   // Called from XRSession::OnFrame handler. Params are background texture
   // mailbox holder and its size respectively.
diff --git a/third_party/blink/renderer/platform/bindings/exception_state.cc b/third_party/blink/renderer/platform/bindings/exception_state.cc
index 137dbb4..2f8edd76 100644
--- a/third_party/blink/renderer/platform/bindings/exception_state.cc
+++ b/third_party/blink/renderer/platform/bindings/exception_state.cc
@@ -205,16 +205,6 @@
                exception);
 }
 
-void ExceptionState::RethrowV8Exception(v8::Local<v8::Value> value) {
-#if DCHECK_IS_ON()
-  DCHECK_AT(!assert_no_exceptions_, file_, line_)
-      << "A V8 exception should not be thrown.";
-#endif
-  SetException(
-      static_cast<ExceptionCode>(InternalExceptionType::kRethrownException),
-      String(), isolate_ ? value : v8::Local<v8::Value>());
-}
-
 void ExceptionState::RethrowV8Exception(v8::TryCatch& try_catch) {
 #if DCHECK_IS_ON()
   DCHECK_AT(!assert_no_exceptions_, file_, line_)
diff --git a/third_party/blink/renderer/platform/bindings/exception_state.h b/third_party/blink/renderer/platform/bindings/exception_state.h
index 10e874c..0b77400 100644
--- a/third_party/blink/renderer/platform/bindings/exception_state.h
+++ b/third_party/blink/renderer/platform/bindings/exception_state.h
@@ -139,8 +139,6 @@
   NOINLINE void ThrowTypeError(const char* message);
   NOINLINE void ThrowWasmCompileError(const char* message);
 
-  // Rethrows a v8::Value as an exception.
-  NOINLINE void RethrowV8Exception(v8::Local<v8::Value>);
   // Report the given value as the exception being thrown, but rethrow it
   // immediately via the v8::TryCatch instead of in the destructor.
   NOINLINE void RethrowV8Exception(v8::TryCatch&);
diff --git a/third_party/blink/renderer/platform/bindings/to_blink_string.cc b/third_party/blink/renderer/platform/bindings/to_blink_string.cc
index e3112ba..abf9734 100644
--- a/third_party/blink/renderer/platform/bindings/to_blink_string.cc
+++ b/third_party/blink/renderer/platform/bindings/to_blink_string.cc
@@ -82,8 +82,9 @@
       32 / sizeof(typename V8StringTrait::CharType);
   if (length <= kInlineBufferSize) {
     typename V8StringTrait::CharType inline_buffer[kInlineBufferSize];
-    V8StringTrait::Write(isolate, v8_string, inline_buffer);
-    return AtomicString(inline_buffer, static_cast<unsigned>(length));
+    base::span<typename V8StringTrait::CharType> buffer_span(inline_buffer);
+    V8StringTrait::Write(isolate, v8_string, buffer_span);
+    return AtomicString(buffer_span.first(static_cast<size_t>(length)));
   }
   base::span<typename V8StringTrait::CharType> buffer;
   String string = String::CreateUninitialized(length, buffer);
diff --git a/third_party/blink/renderer/platform/fonts/opentype/font_settings.cc b/third_party/blink/renderer/platform/fonts/opentype/font_settings.cc
index b8cb230..baa31f1 100644
--- a/third_party/blink/renderer/platform/fonts/opentype/font_settings.cc
+++ b/third_party/blink/renderer/platform/fonts/opentype/font_settings.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/platform/fonts/opentype/font_settings.h"
 
+#include <array>
+
 #include "third_party/blink/renderer/platform/wtf/hash_functions.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
@@ -12,17 +14,16 @@
 
 namespace blink {
 
-uint32_t AtomicStringToFourByteTag(AtomicString tag) {
+uint32_t AtomicStringToFourByteTag(const AtomicString& tag) {
   DCHECK_EQ(tag.length(), 4u);
   return (((tag[0]) << 24) | ((tag[1]) << 16) | ((tag[2]) << 8) | (tag[3]));
 }
 
 AtomicString FourByteTagToAtomicString(uint32_t tag) {
-  constexpr size_t tag_size = 4;
-  LChar tag_string[tag_size] = {
+  const std::array<LChar, 4> tag_string = {
       static_cast<LChar>(tag >> 24), static_cast<LChar>(tag >> 16),
       static_cast<LChar>(tag >> 8), static_cast<LChar>(tag)};
-  return AtomicString(tag_string, tag_size);
+  return AtomicString(tag_string);
 }
 
 unsigned FontVariationSettings::GetHash() const {
diff --git a/third_party/blink/renderer/platform/fonts/opentype/font_settings.h b/third_party/blink/renderer/platform/fonts/opentype/font_settings.h
index 7c8360a..ba6bbb3 100644
--- a/third_party/blink/renderer/platform/fonts/opentype/font_settings.h
+++ b/third_party/blink/renderer/platform/fonts/opentype/font_settings.h
@@ -15,7 +15,7 @@
 
 namespace blink {
 
-PLATFORM_EXPORT uint32_t AtomicStringToFourByteTag(AtomicString tag);
+PLATFORM_EXPORT uint32_t AtomicStringToFourByteTag(const AtomicString& tag);
 PLATFORM_EXPORT AtomicString FourByteTagToAtomicString(uint32_t tag);
 
 template <typename T>
diff --git a/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc b/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
index be101a7..337c4449 100644
--- a/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
+++ b/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
@@ -105,7 +105,7 @@
       intrinsic_type =
           input.calc_size_keyword_behavior == CalcSizeKeywordBehavior::kAsAuto
               ? Length::Type::kAuto
-              : Length::Type::kFillAvailable;
+              : Length::Type::kStretch;
       break;
   }
 
diff --git a/third_party/blink/renderer/platform/geometry/length.cc b/third_party/blink/renderer/platform/geometry/length.cc
index 827b3bf..f4e6240 100644
--- a/third_party/blink/renderer/platform/geometry/length.cc
+++ b/third_party/blink/renderer/platform/geometry/length.cc
@@ -41,7 +41,7 @@
 namespace blink {
 
 PLATFORM_EXPORT DEFINE_GLOBAL(Length, g_auto_length);
-PLATFORM_EXPORT DEFINE_GLOBAL(Length, g_fill_available_length);
+PLATFORM_EXPORT DEFINE_GLOBAL(Length, g_stretch_length);
 PLATFORM_EXPORT DEFINE_GLOBAL(Length, g_fit_content_length);
 PLATFORM_EXPORT DEFINE_GLOBAL(Length, g_max_content_length);
 PLATFORM_EXPORT DEFINE_GLOBAL(Length, g_min_content_length);
@@ -50,8 +50,7 @@
 // static
 void Length::Initialize() {
   new (WTF::NotNullTag::kNotNull, (void*)&g_auto_length) Length(kAuto);
-  new (WTF::NotNullTag::kNotNull, (void*)&g_fill_available_length)
-      Length(kFillAvailable);
+  new (WTF::NotNullTag::kNotNull, (void*)&g_stretch_length) Length(kStretch);
   new (WTF::NotNullTag::kNotNull, (void*)&g_fit_content_length)
       Length(kFitContent);
   new (WTF::NotNullTag::kNotNull, (void*)&g_max_content_length)
@@ -253,14 +252,14 @@
   if (GetType() == kCalculated) {
     return GetCalculationValue().HasPercentOrStretch();
   }
-  return GetType() == kPercent || GetType() == kFillAvailable;
+  return GetType() == kPercent || GetType() == kStretch;
 }
 
 bool Length::HasStretch() const {
   if (GetType() == kCalculated) {
     return GetCalculationValue().HasStretch();
   }
-  return GetType() == kFillAvailable;
+  return GetType() == kStretch;
 }
 
 bool Length::HasMinContent() const {
@@ -294,10 +293,9 @@
   StringBuilder builder;
   builder.Append("Length(");
   static const char* const kTypeNames[] = {
-      "Auto",         "Percent",      "Fixed",         "MinContent",
-      "MaxContent",   "MinIntrinsic", "FillAvailable", "FitContent",
-      "Calculated",   "Flex",         "ExtendToZoom",  "DeviceWidth",
-      "DeviceHeight", "None",         "Content"};
+      "Auto",         "Percent",     "Fixed",        "MinContent", "MaxContent",
+      "MinIntrinsic", "Stretch",     "FitContent",   "Calculated", "Flex",
+      "ExtendToZoom", "DeviceWidth", "DeviceHeight", "None",       "Content"};
   if (type_ < std::size(kTypeNames))
     builder.Append(kTypeNames[type_]);
   else
diff --git a/third_party/blink/renderer/platform/geometry/length.h b/third_party/blink/renderer/platform/geometry/length.h
index 2251b63e..f0e3a6d 100644
--- a/third_party/blink/renderer/platform/geometry/length.h
+++ b/third_party/blink/renderer/platform/geometry/length.h
@@ -93,7 +93,7 @@
 class Length;
 
 PLATFORM_EXPORT extern const Length& g_auto_length;
-PLATFORM_EXPORT extern const Length& g_fill_available_length;
+PLATFORM_EXPORT extern const Length& g_stretch_length;
 PLATFORM_EXPORT extern const Length& g_fit_content_length;
 PLATFORM_EXPORT extern const Length& g_max_content_length;
 PLATFORM_EXPORT extern const Length& g_min_content_length;
@@ -117,7 +117,7 @@
     kMinContent,
     kMaxContent,
     kMinIntrinsic,
-    kFillAvailable,
+    kStretch,
     kFitContent,
     kCalculated,
     kFlex,
@@ -190,7 +190,7 @@
   bool operator!=(const Length& o) const { return !(*this == o); }
 
   static const Length& Auto() { return g_auto_length; }
-  static const Length& FillAvailable() { return g_fill_available_length; }
+  static const Length& Stretch() { return g_stretch_length; }
   static const Length& FitContent() { return g_fit_content_length; }
   static const Length& MaxContent() { return g_max_content_length; }
   static const Length& MinContent() { return g_min_content_length; }
@@ -307,7 +307,7 @@
   bool IsMinContent() const { return GetType() == kMinContent; }
   bool IsMaxContent() const { return GetType() == kMaxContent; }
   bool IsMinIntrinsic() const { return GetType() == kMinIntrinsic; }
-  bool IsFillAvailable() const { return GetType() == kFillAvailable; }
+  bool IsStretch() const { return GetType() == kStretch; }
   bool IsFitContent() const { return GetType() == kFitContent; }
   bool IsPercent() const { return GetType() == kPercent; }
   // MayHavePercentDependence should be used to decide whether to optimize
diff --git a/third_party/blink/renderer/platform/geometry/length_functions.cc b/third_party/blink/renderer/platform/geometry/length_functions.cc
index 8471cd0..2ce95de 100644
--- a/third_party/blink/renderer/platform/geometry/length_functions.cc
+++ b/third_party/blink/renderer/platform/geometry/length_functions.cc
@@ -43,7 +43,7 @@
       return length.GetFloatValue();
     case Length::kPercent:
       return ClampTo<float>(maximum_value * length.Percent() / 100.0f);
-    case Length::kFillAvailable:
+    case Length::kStretch:
     case Length::kAuto:
       return static_cast<float>(maximum_value);
     case Length::kCalculated:
@@ -76,7 +76,7 @@
           static_cast<float>(maximum_value * length.Percent() / 100.0f));
     case Length::kCalculated:
       return LayoutUnit(length.NonNanCalculatedValue(maximum_value, input));
-    case Length::kFillAvailable:
+    case Length::kStretch:
     case Length::kAuto:
       return LayoutUnit();
     case Length::kFixed:
@@ -105,7 +105,7 @@
     case Length::kPercent:
     case Length::kCalculated:
       return MinimumValueForLength(length, maximum_value, input);
-    case Length::kFillAvailable:
+    case Length::kStretch:
     case Length::kAuto:
       return maximum_value;
     case Length::kMinContent:
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_response.cc b/third_party/blink/renderer/platform/loader/fetch/resource_response.cc
index 26825375..ea908ab 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_response.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_response.cc
@@ -464,9 +464,7 @@
 AtomicString ResourceResponse::ConnectionInfoString() const {
   std::string_view connection_info_string =
       net::HttpConnectionInfoToString(connection_info_);
-  return AtomicString(
-      reinterpret_cast<const LChar*>(connection_info_string.data()),
-      connection_info_string.length());
+  return AtomicString(base::as_byte_span(connection_info_string));
 }
 
 mojom::blink::CacheState ResourceResponse::CacheState() const {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 8dd3a111..b032ea6 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2256,7 +2256,7 @@
     },
     {
       name: "HighlightInheritance",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "HighlightPointerEvents",
@@ -2392,6 +2392,11 @@
       status: "stable",
     },
     {
+      // crbug.com/370217727
+      name: "InitialLetterRaiseBySpecified",
+      status: "stable",
+    },
+    {
       name: "InlineBlockInSameLine",
       status: "stable",
     },
diff --git a/third_party/blink/renderer/platform/weborigin/kurl.cc b/third_party/blink/renderer/platform/weborigin/kurl.cc
index 87a36fb..017c5d3c 100644
--- a/third_party/blink/renderer/platform/weborigin/kurl.cc
+++ b/third_party/blink/renderer/platform/weborigin/kurl.cc
@@ -960,13 +960,11 @@
   // expensive for large URLs. However, since many URLs are generated from
   // existing AtomicStrings (which already have their hashes computed), the fast
   // path can often avoid this work.
-  if (!relative.IsNull() &&
-      StringView(output.data(), static_cast<unsigned>(output.length())) ==
-          relative) {
+  const auto output_url_span = base::as_byte_span(output.view());
+  if (!relative.IsNull() && StringView(output_url_span) == relative) {
     string_ = AtomicString(relative.Impl());
   } else {
-    string_ =
-        AtomicString(reinterpret_cast<LChar*>(output.data()), output.length());
+    string_ = AtomicString(output_url_span);
   }
 
   InitProtocolMetadata();
@@ -1092,8 +1090,7 @@
   if (replacements_valid || !preserve_validity) {
     is_valid_ = replacements_valid;
     parsed_ = new_parsed;
-    string_ =
-        AtomicString(reinterpret_cast<LChar*>(output.data()), output.length());
+    string_ = AtomicString(base::as_byte_span(output.view()));
     InitProtocolMetadata();
     AssertStringSpecIsASCII();
   }
diff --git a/third_party/blink/renderer/platform/wtf/text/atomic_string.cc b/third_party/blink/renderer/platform/wtf/text/atomic_string.cc
index 8b8b554..fb5d8a7 100644
--- a/third_party/blink/renderer/platform/wtf/text/atomic_string.cc
+++ b/third_party/blink/renderer/platform/wtf/text/atomic_string.cc
@@ -47,11 +47,21 @@
 
 AtomicString::AtomicString(const LChar* chars, unsigned length)
     : string_(AtomicStringTable::Instance().Add(chars, length)) {}
+AtomicString::AtomicString(base::span<const LChar> chars)
+    : string_(AtomicStringTable::Instance().Add(
+          chars.data(),
+          base::checked_cast<wtf_size_t>(chars.size()))) {}
 
 AtomicString::AtomicString(const UChar* chars,
                            unsigned length,
                            AtomicStringUCharEncoding encoding)
     : string_(AtomicStringTable::Instance().Add(chars, length, encoding)) {}
+AtomicString::AtomicString(base::span<const UChar> chars,
+                           AtomicStringUCharEncoding encoding)
+    : string_(AtomicStringTable::Instance().Add(
+          chars.data(),
+          base::checked_cast<wtf_size_t>(chars.size()),
+          encoding)) {}
 
 AtomicString::AtomicString(const UChar* chars)
     : string_(AtomicStringTable::Instance().Add(
diff --git a/third_party/blink/renderer/platform/wtf/text/atomic_string.h b/third_party/blink/renderer/platform/wtf/text/atomic_string.h
index dbadd1c..277d1d9 100644
--- a/third_party/blink/renderer/platform/wtf/text/atomic_string.h
+++ b/third_party/blink/renderer/platform/wtf/text/atomic_string.h
@@ -69,10 +69,14 @@
   explicit AtomicString(const char* chars)
       : AtomicString(reinterpret_cast<const LChar*>(chars)) {}
   AtomicString(const LChar* chars, unsigned length);
+  explicit AtomicString(base::span<const LChar> chars);
   AtomicString(
       const UChar* chars,
       unsigned length,
       AtomicStringUCharEncoding encoding = AtomicStringUCharEncoding::kUnknown);
+  explicit AtomicString(
+      base::span<const UChar> chars,
+      AtomicStringUCharEncoding encoding = AtomicStringUCharEncoding::kUnknown);
   explicit AtomicString(const UChar* chars);
 
   explicit AtomicString(const StringView& view);
diff --git a/third_party/blink/renderer/platform/wtf/text/string_view.cc b/third_party/blink/renderer/platform/wtf/text/string_view.cc
index 06af1dd..e321f89 100644
--- a/third_party/blink/renderer/platform/wtf/text/string_view.cc
+++ b/third_party/blink/renderer/platform/wtf/text/string_view.cc
@@ -208,8 +208,8 @@
   if (StringImpl* impl = SharedImpl())
     return AtomicString(impl);
   if (Is8Bit())
-    return AtomicString(Characters8(), length_);
-  return AtomicString(Characters16(), length_);
+    return AtomicString(Span8());
+  return AtomicString(Span16());
 }
 
 String StringView::EncodeForDebugging() const {
diff --git a/third_party/blink/tools/gdb/blink.py b/third_party/blink/tools/gdb/blink.py
index 9241cfd..7a600997 100644
--- a/third_party/blink/tools/gdb/blink.py
+++ b/third_party/blink/tools/gdb/blink.py
@@ -290,7 +290,7 @@
         if ltype == 5:
             return 'Length(MinIntrinsic)'
         if ltype == 6:
-            return 'Length(FillAvailable)'
+            return 'Length(Stretch)'
         if ltype == 7:
             return 'Length(FitContent)'
         if ltype == 8:
diff --git a/third_party/blink/tools/lldb/lldb_blink.py b/third_party/blink/tools/lldb/lldb_blink.py
index 68e091a..790b112 100644
--- a/third_party/blink/tools/lldb/lldb_blink.py
+++ b/third_party/blink/tools/lldb/lldb_blink.py
@@ -271,7 +271,7 @@
         if ltype == 6:
             return 'Length(MaxContent)'
         if ltype == 7:
-            return 'Length(FillAvailable)'
+            return 'Length(Stretch)'
         if ltype == 8:
             return 'Length(FitContent)'
         if ltype == 9:
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 45c477d..7b71861 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1212,6 +1212,10 @@
 crbug.com/359926563 external/wpt/css/css-inline/text-box-trim/text-box-trim-half-leading-block-box-004.html [ Failure ]
 crbug.com/359926563 external/wpt/css/css-inline/text-box-trim/text-box-trim-half-leading-block-box-005.html [ Failure ]
 
+# CSS Inline `initial-letter` property.
+crbug.com/370423031 external/wpt/css/css-inline/initial-letter/initial-letter-float-001-vlr.html [ Failure ]
+crbug.com/370423031 external/wpt/css/css-inline/initial-letter/initial-letter-float-001-vrl.html [ Failure ]
+
 # LayoutNG ref-tests that need to be updated (cannot be rebaselined).
 crbug.com/591099 [ Win ] virtual/text-antialias/ellipsis-with-self-painting-layer.html [ Failure ]
 crbug.com/1098801 virtual/text-antialias/whitespace/whitespace-in-pre.html [ Failure ]
@@ -7979,9 +7983,6 @@
 # Gardener 2024-07-30
 crbug.com/356177815 [ Win11-arm64 ] http/tests/cookies/partitioned-cookies/partitioned-cookies-in-parallel-frames.https.html [ Failure Pass Timeout ]
 
-# Gardener 2024-07-31
-crbug.com/356566702 [ Win ] external/wpt/css/css-color/parsing/color-valid-relative-color.html [ Failure Pass ]
-
 # Gardener 2024-08-02
 crbug.com/356963272 [ Linux ] external/wpt/webrtc/RTCDataChannel-send-close.html [ Failure Pass Timeout ]
 
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 8499068..eb9fbfb8 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -332,7 +332,7 @@
     "bases": ["images"],
     "args": ["--force-color-profile=srgb",
              "--force-raster-color-profile=color-spin-gamma24"],
-    "expires": "Jul 1, 2023"
+    "expires": "Jul 1, 2025"
   },
   "The stable suite tests for conformance to HTML/web specifications, and",
   "detects unintentional changes to web exposed surface, so should not expire.",
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vlr-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vlr-ref.html
index d43b2e39..2c047b0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vlr-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vlr-ref.html
@@ -35,7 +35,7 @@
         background: lime;
         float: left;
         height: 80px;
-        margin-left: 8px;
+        margin-left: -4px;
         width: 80px;
     }
 </style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vrl-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vrl-ref.html
index 0fe5e74..ce2b411 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vrl-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vrl-ref.html
@@ -35,7 +35,7 @@
         background: lime;
         float: left;
         height: 80px;
-        margin-right: 8px;
+        margin-right: -4px;
         width: 80px;
     }
 </style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-vlr-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-vlr-ref.html
index 6bbc5c3..677e2a4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-vlr-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-vlr-ref.html
@@ -21,7 +21,7 @@
         float: left;
         height: 80px;
         margin-bottom: 30px;
-        margin-left: 23px;
+        margin-left: 11px;
         margin-right: 45px;
         margin-top: 10px;
         width: 80px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-vrl-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-vrl-ref.html
index d608d256c..532f3a7d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-vrl-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-vrl-ref.html
@@ -22,7 +22,7 @@
         height: 80px;
         margin-bottom: 30px;
         margin-left: 15px;
-        margin-right: 53px;
+        margin-right: 41px;
         margin-top: 10px;
         width: 80px;
     }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-over-ruby-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-over-ruby-ref.html
index 43222b4..23401fd 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-over-ruby-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-over-ruby-ref.html
@@ -31,7 +31,7 @@
 </head>
 <body>
 <div class="sample">
-<div class="fake-initial-letter"></div><br><br><br>
+<div class="fake-initial-letter"></div><br><br>
 <div class="surrounding">bc <ruby>xyz<rt>XYZ</rt></ruby></div>
 def<br>ghi<br>jkl<br>mno<br>
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-over-ruby-tall-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-over-ruby-tall-ref.html
index f1086074..9511f3e6f2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-over-ruby-tall-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-over-ruby-tall-ref.html
@@ -21,7 +21,7 @@
         background: lime;
         float: left;
         height: 80px;
-        margin-top: 8px;
+        margin-top: 32px;
         width: 80px;
     }
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby-ref.html
index 9adde375..87bc6d0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby-ref.html
@@ -32,7 +32,7 @@
 </head>
 <body>
 <div class="sample">
-<div class="fake-initial-letter"></div><br><br><br>
+<div class="fake-initial-letter"></div><br><br>
 <div class="surrounding">bc <ruby>xyz<rt>XYZ</rt></ruby></div>
 def<br>ghi<br>jkl<br>mno<br>
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby-tall-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby-tall-ref.html
index 7e334e5..e818de6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby-tall-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby-tall-ref.html
@@ -25,7 +25,7 @@
         width: 80px;
     }
 
-    .surrounding { display:inline-block; margin-top: 72px; margin-bottom: -2px; }
+    .surrounding { display:inline-block; margin-top: 48px; margin-bottom: -2px; }
 </style>
 </head>
 <body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vlr-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vlr-ref.html
index f28d1eed..5447e2f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vlr-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vlr-ref.html
@@ -20,7 +20,7 @@
         background: lime;
         float: left;
         height: 80px;
-        margin-left: 8px;
+        margin-left: -4px;
         width: 80px;
     }
 </style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vrl-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vrl-ref.html
index 4ec34f4c..e1e04ccf 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vrl-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vrl-ref.html
@@ -20,7 +20,7 @@
         background: lime;
         float: left;
         height: 80px;
-        margin-right: 8px;
+        margin-right: -4px;
         width: 80px;
     }
 </style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-vlr-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-vlr-ref.html
index 3982a36..b4567d2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-vlr-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-vlr-ref.html
@@ -28,7 +28,7 @@
         background: lime;
         float: left;
         height: 80px;
-        margin-left: 8px;
+        margin-left: -4px;
         width: 80px;
     }
 </style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-ref.html
index f59625a..29594f4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
-<html>
-<head>
+<meta charset="utf-8">
 <title>Tests initial letter raise initial</title>
 <link rel="author" title="Google LLC" href="https://www.google.com/">
 <link rel="help" href="https://drafts.csswg.org/css-inline/#raise-initial">
 <meta name="flags" content="ahem">
 <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" >
+<script src="resources/initial-letter-variants.js"></script>
 <style>
     .sample {
         border: solid 1px green;
@@ -18,16 +18,27 @@
     .fake-initial-letter {
         background: lime;
         float: left;
-        height: 80px;
+        --cap: 0.8;
+        --size: calc((24px * 2 + 20px * var(--cap)) / var(--cap));
+        height: var(--size);
+        width: var(--size);
         margin-top: 2px;
-        width: 80px;
+    }
+
+    .no-ascent .fake-initial-letter {
+        height: calc(var(--size) * (1 - var(--cap)));
+        margin-top: calc(var(--size) * var(--cap) + 2px);
+    }
+    .no-descent .fake-initial-letter {
+        height: calc(var(--size) * var(--cap));
     }
 </style>
 </head>
 <body>
 <div class="sample">
-<div class="fake-initial-letter"></div><br><br><br>
+<div class="fake-initial-letter"></div><br><br>
 bc<br>def<br>ghi<br>jkl<br>mno<br>
 </div>
-</body>
-</html>
+<script>
+setupInitialLetterTestVariants();
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-rtl-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-rtl-ref.html
index 9dce9e6e..e5dd8f96 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-rtl-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-rtl-ref.html
@@ -27,7 +27,7 @@
 </head>
 <body>
 <div class="sample">
-<div class="fake-initial-letter"></div><br><br><br>
+<div class="fake-initial-letter"></div><br><br>
 bc<br>def<br>ghi<br>jkl<br>mno<br>
 </div>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-vlr-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-vlr-ref.html
index 58e5c33..72d2dd0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-vlr-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-vlr-ref.html
@@ -20,7 +20,7 @@
         background: lime;
         display: inline-block;
         height: 80px;
-        margin-left: 8px;
+        margin-left: -4px;
         margin-right: 8px;
         width: 80px;
     }
@@ -29,14 +29,19 @@
         display: inline-block;
         vertical-align: top;
         margin-top: -20px;
+        margin-right: -12px;
+    }
+
+    .remainder2 {
+        margin-left: 12px;
     }
 </style>
 </head>
 <body>
 <div class="sample">
 <div class="fake-initial-letter"></div>
-<div class="remainder">bc</div><br>
-def<br>ghi<br>jkl<br>mno<br>
+<div class="remainder">bc<br>def</div>
+<div class="remainder2">ghi<br>jkl<br>mno</div>
 </div>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-vrl-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-vrl-ref.html
index 5fa6845..38be19283 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-vrl-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial-vrl-ref.html
@@ -20,8 +20,8 @@
         background: lime;
         display: inline-block;
         height: 80px;
-        margin-left: 8px;
-        margin-right: 8px;
+        margin-left: 20px;
+        margin-right: -4px;
         width: 80px;
     }
 
@@ -35,8 +35,8 @@
 <body>
 <div class="sample">
 <div class="fake-initial-letter"></div>
-<div class="remainder">bc</div><br>
-def<br>ghi<br>jkl<br>mno<br>
+<div class="remainder">bc<br>def</div><br>
+ghi<br>jkl<br>mno<br>
 </div>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial.html
index 4b8e8145..ec2ec6bd 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raise-initial.html
@@ -1,12 +1,14 @@
 <!DOCTYPE html>
-<html>
-<head>
 <title>Tests initial letter raise initial</title>
 <link rel="author" title="Google LLC" href="https://www.google.com/">
 <link rel="help" href="https://drafts.csswg.org/css-inline/#raise-initial">
 <meta name="flags" content="ahem">
 <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" >
 <link rel="match" href="initial-letter-raise-initial-ref.html">
+<script src="resources/initial-letter-variants.js"></script>
+<meta name="variant" content="?class=">
+<meta name="variant" content="?class=no-ascent">
+<meta name="variant" content="?class=no-descent">
 <style>
     .sample {
         border: solid 1px green;
@@ -28,5 +30,3 @@
 <div class="sample initial-letter">
 Abc<br>def<br>ghi<br>jkl<br>mno<br>
 </div>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-raise-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-raise-ref.html
index 7285ed8a..0a134fc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-raise-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-raise-ref.html
@@ -27,7 +27,7 @@
 <body>
 <div>This line before initial letter.</div>
 <div class="sample">
-<div class="fake-initial-letter"></div><br><br><br>
+<div class="fake-initial-letter"></div><br><br>
 bc<br>def<br>ghi<br>jkl<br>mno<br>
 </div>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-sunken-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-sunken-ref.html
index a68a760..41031ff8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-sunken-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-sunken-ref.html
@@ -27,7 +27,7 @@
 <body>
 <div>This line before initial letter.</div>
 <div class="sample">
-<div class="fake-initial-letter"></div><br><br>
+<div class="fake-initial-letter"></div><br>
 bc<br>def<br>ghi<br>jkl<br>mno<br>
 </div>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-ref.html
index 9b036a2b..0b56917 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-ref.html
@@ -6,6 +6,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-inline/#sunk-initial">
 <meta name="flags" content="ahem">
 <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" >
+<script src="resources/initial-letter-variants.js"></script>
 <style>
     .sample {
         border: solid 1px green;
@@ -18,15 +19,25 @@
     .fake-initial-letter {
         background: lime;
         float: left;
-        height: 80px;
+        --cap: 0.8;
+        --size: calc((24px * 2 + 20px * var(--cap)) / var(--cap));
+        height: var(--size);
+        width: var(--size);
         margin-top: 2px;
-        width: 80px;
+    }
+
+    .no-ascent .fake-initial-letter {
+        height: calc(var(--size) * (1 - var(--cap)));
+        margin-top: calc(var(--size) * var(--cap) + 2px);
+    }
+    .no-descent .fake-initial-letter {
+        height: calc(var(--size) * var(--cap));
     }
 </style>
 </head>
 <body>
 <div class="sample">
-<div class="fake-initial-letter"></div><br><br>
+<div class="fake-initial-letter"></div><br>
 bc<br>def<br>ghi<br>jkl<br>mno<br>
 </div>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-rtl-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-rtl-ref.html
index 3318c7e..cb26ab0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-rtl-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-rtl-ref.html
@@ -27,7 +27,7 @@
 </head>
 <body>
 <div class="sample">
-<div class="fake-initial-letter"></div><br><br>
+<div class="fake-initial-letter"></div><br>
 bc<br>def<br>ghi<br>jkl<br>mno<br>
 </div>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vlr-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vlr-ref.html
index c3ccef2..ebdb5a1 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vlr-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vlr-ref.html
@@ -20,14 +20,14 @@
         background: lime;
         display: inline-block;
         height: 80px;
-        margin-left: 8px;
+        margin-left: -4px;
         margin-right: 8px;
         width: 80px;
     }
 
     .remainder {
         display: inline-block;
-        margin-left: 48px;
+        margin-left: 24px;
         margin-top: -20px;
         vertical-align: bottom;
     }
@@ -40,8 +40,8 @@
 <body>
 <div class="sample">
 <div class="fake-initial-letter"></div>
-<div class="remainder">bc<br>def<br></div><br>
-<div class="remainder2">ghi<br>jkl<br>mno<br></div>
+<div class="remainder">bc<br>def<br>ghi<br></div><br>
+<div class="remainder2">jkl<br>mno<br></div>
 </div>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vrl-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vrl-ref.html
index a7db8b9..18735029 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vrl-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vrl-ref.html
@@ -21,13 +21,13 @@
         display: inline-block;
         height: 80px;
         margin-left: 8px;
-        margin-right: 8px;
+        margin-right: -4px;
         width: 80px;
     }
 
     .remainder {
         display: inline-block;
-        margin-right: 48px;
+        margin-right: 24px;
         margin-top: -20px;
         vertical-align: top;
     }
@@ -40,8 +40,8 @@
 <body>
 <div class="sample">
 <div class="fake-initial-letter"></div>
-<div class="remainder">bc<br>def</div><br>
-<div class="remainder2">ghi<br>jkl<br>mno<br></div>
+<div class="remainder">bc<br>def<br>ghi</div><br>
+<div class="remainder2">jkl<br>mno<br></div>
 </div>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial.html b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial.html
index 701469c..3c00662d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial.html
@@ -1,12 +1,14 @@
 <!DOCTYPE html>
-<html>
-<head>
 <title>Tests initial letter sunken initial</title>
 <link rel="author" title="Google LLC" href="https://www.google.com/">
 <link rel="help" href="https://drafts.csswg.org/css-inline/#sunk-initial">
 <meta name="flags" content="ahem">
 <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" >
 <link rel="match" href="initial-letter-sunk-initial-ref.html">
+<script src="resources/initial-letter-variants.js"></script>
+<meta name="variant" content="?class=">
+<meta name="variant" content="?class=no-ascent">
+<meta name="variant" content="?class=no-descent">
 <style>
     .sample {
         border: solid 1px green;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/resources/initial-letter-variants.js b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/resources/initial-letter-variants.js
new file mode 100644
index 0000000..a3eceae
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/resources/initial-letter-variants.js
@@ -0,0 +1,75 @@
+window.addEventListener('load', setupInitialLetterTestVariants);
+
+function setupInitialLetterTestVariants() {
+  const search = window.location.search;
+  if (!search) {
+    return;
+  }
+  const params = new URLSearchParams(search);
+  const classes = params.getAll('class')
+                  .flatMap(value => value.split(','))
+                  .filter(value => value);
+  let text = params.getAll('text').join('');
+  if (!text) {
+    if (classes.indexOf('no-descent') >= 0) {
+      text = '\xC9\xC9M\xC9';
+    } else if (classes.indexOf('no-ascent') >= 0) {
+      text = 'ppMp';
+    }
+  }
+
+  for (const element of document.getElementsByClassName('sample')) {
+    element.classList.add(...classes);
+    if (text) {
+      replaceTextStart(element, text);
+    }
+  }
+}
+
+// Replace the start of the text content of the node.
+// Returns the number of characters replaced.
+//
+// For example,
+// `replaceTextStart(element, 'XY')` to the content:
+// ```
+// <div>ABC</div>
+// ```
+// produces:
+// ```
+// <div>XYC</div>
+// ```
+//
+// It has a limited support for separated text nodes and collapsible spaces.
+function replaceTextStart(node, text) {
+  if (node.nodeType == Node.TEXT_NODE) {
+    const content = node.nodeValue;
+    const trimmed_content = content.trimStart();
+    if (!trimmed_content) {
+      return 0;
+    }
+    const leading_spaces_len = content.length - trimmed_content.length;
+    const len = Math.min(text.length, trimmed_content.length);
+    node.nodeValue = content.substring(0, leading_spaces_len) +
+                     text.substring(0, len) +
+                     trimmed_content.substring(len);
+    return len;
+  }
+
+  if (node.nodeType == Node.ELEMENT_NODE && node.className.indexOf('fake') >= 0) {
+    // If this is a fake initial letter, pretend that one character is replaced.
+    return 1;
+  }
+
+  let total_replaced = 0;
+  for (const child of node.childNodes) {
+    const replaced = replaceTextStart(child, text);
+    if (replaced) {
+      total_replaced += replaced;
+      text = text.substring(replaced);
+      if (!text) {
+        return total_replaced;
+      }
+    }
+  }
+  return total_replaced;
+}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed-inheritance.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed-inheritance.html
index d67ae82..90bfc52 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed-inheritance.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed-inheritance.html
@@ -18,6 +18,14 @@
     color: currentcolor;
     background-color: currentcolor;
   }
+  #target::search-text {
+    color: currentcolor;
+    background-color: currentcolor;
+  }
+  #target::search-text:current {
+    color: red;
+    background-color: red;
+  }
   #target::spelling-error {
     color: currentcolor;
     background-color: currentcolor;
@@ -33,7 +41,7 @@
 </style>
 <div id="target"><span id="child"></span></div>
 <script>
-  for (const pseudo of ["::selection", "::target-text", "::spelling-error", "::grammar-error", "::highlight(foo)"]) {
+  for (const pseudo of ["::selection", "::target-text", "::search-text", "::spelling-error", "::grammar-error", "::highlight(foo)"]) {
     test(() => {
       let style = getComputedStyle(child, pseudo);
       assert_equals(style.backgroundColor, "rgb(0, 255, 0)", "Background color is lime.");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed-visited.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed-visited.html
index 207cb7b..21f3b38 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed-visited.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed-visited.html
@@ -18,6 +18,12 @@
   a:visited::target-text {
     color: currentcolor;
   }
+  a:visited::search-text {
+    color: currentcolor;
+  }
+  a:visited::search-text:current {
+    color: red;
+  }
   a:visited::spelling-error {
     color: currentcolor;
   }
@@ -31,7 +37,7 @@
 <a id="target1" class="target" href=""></a>
 <a id="target2" class="target" href="unvisited"></a>
 <script>
-  for (const pseudo of ["::selection", "::target-text", "::spelling-error", "::grammar-error", "::highlight(foo)"]) {
+  for (const pseudo of ["::selection", "::target-text", "::search-text", "::spelling-error", "::grammar-error", "::highlight(foo)"]) {
     for (const target of [target1, target2]) {
       test(() => {
         let style = getComputedStyle(target, pseudo);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed.html
index 97c3180..c9c493bd 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-currentcolor-computed.html
@@ -38,6 +38,14 @@
     color: currentcolor;
     background-color: currentcolor;
   }
+  .target::search-text {
+    color: currentcolor;
+    background-color: currentcolor;
+  }
+  .target::search-text:current {
+    color: red;
+    background-color: red;
+  }
   .target::spelling-error {
     color: currentcolor;
     background-color: currentcolor;
@@ -56,7 +64,7 @@
   <span id="target2" class="target"></span>
 </div>
 <script>
-  for (const pseudo of ["::selection", "::target-text", "::spelling-error", "::grammar-error", "::highlight(foo)"]) {
+  for (const pseudo of ["::selection", "::target-text", "::search-text", "::spelling-error", "::grammar-error", "::highlight(foo)"]) {
     for (const target of [target1, target2]) {
       test(() => {
         let style = getComputedStyle(target, pseudo);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-pseudos-inheritance-computed-001.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-pseudos-inheritance-computed-001.html
index 84c4045..4c89d0d0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-pseudos-inheritance-computed-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-pseudos-inheritance-computed-001.html
@@ -14,6 +14,14 @@
     background-color: green;
     color: lime;
   }
+  .target::search-text {
+    background-color: green;
+    color: lime;
+  }
+  .target::search-text:current {
+    background-color: red;
+    color: red;
+  }
   .target::spelling-error {
     background-color: green;
     color: lime;
@@ -30,7 +38,7 @@
 <div class="target"><span id="child1"></span></div>
 <div class="target" style="display: contents;"><span id="child2"></span></div>
 <script>
-  for (const pseudo of ["::selection", "::target-text", "::spelling-error", "::grammar-error", "::highlight(foo)"]) {
+  for (const pseudo of ["::selection", "::target-text", "::search-text", "::spelling-error", "::grammar-error", "::highlight(foo)"]) {
     for (const child of [child1, child2]) {
       test(() => {
         let style = getComputedStyle(child, pseudo);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-pseudos-visited-computed-001.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-pseudos-visited-computed-001.html
index a2b18ef..074ef932 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-pseudos-visited-computed-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/highlight-cascade/highlight-pseudos-visited-computed-001.html
@@ -11,6 +11,12 @@
   a::target-text {
     color: lime;
   }
+  a::search-text {
+    color: lime;
+  }
+  a::search-text:current {
+    color: red;
+  }
   a::spelling-error {
     color: lime;
   }
@@ -26,6 +32,12 @@
   a:visited::target-text {
     color: yellow;
   }
+  a:visited::search-text {
+    color: yellow;
+  }
+  a:visited::search-text:current {
+    color: red;
+  }
   a:visited::spelling-error {
     color: yellow;
   }
@@ -39,7 +51,7 @@
 <a id="target1" class="target" href=""></a>
 <a id="target2" class="target" href="unvisited"></a>
 <script>
-  for (const pseudo of ["::selection", "::target-text", "::spelling-error", "::grammar-error", "::highlight(foo)"]) {
+  for (const pseudo of ["::selection", "::target-text", "::search-text", "::spelling-error", "::grammar-error", "::highlight(foo)"]) {
     for (const target of [target1, target2]) {
       test(() => {
         let style = getComputedStyle(target, pseudo);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/imagebitmap/createImageBitmap-exif-orientation-expected.txt b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/imagebitmap/createImageBitmap-exif-orientation-expected.txt
deleted file mode 100644
index b56bc43a7..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/imagebitmap/createImageBitmap-exif-orientation-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] createImageBitmap with EXIF rotation, imageOrientation from-image, and cropping
-  assert_array_approx_equals: Pixel value at (40,40) 0,255,0,255 =~ 128,127,254,255. property 0, expected 0 +/- 1, expected 0 but got 128
-[FAIL] createImageBitmap with EXIF rotation, imageOrientation flipY, and cropping
-  assert_array_approx_equals: Pixel value at (40,40) 128,255,128,255 =~ 0,0,0,0. property 0, expected 128 +/- 1, expected 128 but got 0
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/imagebitmap/createImageBitmap-exif-orientation.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/imagebitmap/createImageBitmap-exif-orientation.html
index 8b2a33e8..ee7023a 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/imagebitmap/createImageBitmap-exif-orientation.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/imagebitmap/createImageBitmap-exif-orientation.html
@@ -13,11 +13,11 @@
     });
 }
 
-function checkColors(ctx, w, h, expectedColors) {
+function checkColors(ctx, w, h, tileW, tileH, expectedColors) {
     let data = ctx.getImageData(0, 0, w, h).data;
     for (let [row, col, r, g, b, a] of expectedColors) {
-        let x = col * 80 + 40;
-        let y = row * 80 + 40;
+        let x = col * tileW + tileW / 2;
+        let y = row * tileH + tileH / 2;
         let i = (x + y * w) * 4;
 
         let expected = [r, g, b, a];
@@ -38,7 +38,7 @@
         .then((image) => createImageBitmap(image))
         .then(t.step_func_done(function(imageBitmap) {
             ctx.drawImage(imageBitmap, 0, 0);
-            checkColors(ctx, canvas.width, canvas.height, [
+            checkColors(ctx, canvas.width, canvas.height, 80, 80, [
                 // row, col, r, g, b, a
                 [0, 0, 255, 0, 0, 255],
                 [0, 1, 0, 255, 0, 255],
@@ -63,7 +63,7 @@
         .then((image) => createImageBitmap(image, { imageOrientation: "flipY" }))
         .then(t.step_func_done(function(imageBitmap) {
             ctx.drawImage(imageBitmap, 0, 0);
-            checkColors(ctx, canvas.width, canvas.height, [
+            checkColors(ctx, canvas.width, canvas.height, 80, 80, [
                 // row, col, r, g, b, a
                 [0, 0, 255, 128, 128, 255],
                 [0, 1, 128, 255, 128, 255],
@@ -88,7 +88,7 @@
         .then(image => createImageBitmap(image, 80, 0, 160, 160))
         .then(t.step_func_done(function(imageBitmap) {
             ctx.drawImage(imageBitmap, 0, 0);
-            checkColors(ctx, canvas.width, canvas.height, [
+            checkColors(ctx, canvas.width, canvas.height, 80, 80, [
                 // row, col, r, g, b, a
                 [0, 0, 0, 255, 0, 255],
                 [0, 1, 0, 0, 255, 255],
@@ -109,7 +109,7 @@
         .then(image => createImageBitmap(image, 80, 0, 160, 160, { imageOrientation: "flipY" }))
         .then(t.step_func_done(function(imageBitmap) {
             ctx.drawImage(imageBitmap, 0, 0);
-            checkColors(ctx, canvas.width, canvas.height, [
+            checkColors(ctx, canvas.width, canvas.height, 80, 80, [
                 // row, col, r, g, b, a
                 [0, 0, 128, 255, 128, 255],
                 [0, 1, 128, 128, 255, 255],
@@ -118,4 +118,72 @@
             ]);
         }));
 }, "createImageBitmap with EXIF rotation, imageOrientation flipY, and cropping");
+
+async_test(function(t) {
+    const canvas = document.createElement("canvas");
+    canvas.width = 160;
+    canvas.height = 80;
+    document.body.append(canvas);
+
+    const ctx = canvas.getContext("2d");
+    loadImage("resources/squares_6.jpg")
+        .then((image) => createImageBitmap(image, { resizeWidth:160, resizeHeight:80} ))
+        .then(t.step_func_done(function(imageBitmap) {
+            ctx.drawImage(imageBitmap, 0, 0);
+            checkColors(ctx, canvas.width, canvas.height, 40, 40, [
+                // row, col, r, g, b, a
+                [0, 0, 255, 0, 0, 255],
+                [0, 1, 0, 255, 0, 255],
+                [0, 2, 0, 0, 255, 255],
+                [0, 3, 0, 0, 0, 255],
+                [1, 0, 255, 128, 128, 255],
+                [1, 1, 128, 255, 128, 255],
+                [1, 2, 128, 128, 255, 255],
+                [1, 3, 128, 128, 128, 255],
+            ]);
+        }));
+}, "createImageBitmap with EXIF rotation, imageOrientation from-image, no cropping, and resize");
+
+async_test(function(t) {
+    const canvas = document.createElement("canvas");
+    canvas.width = 80;
+    canvas.height = 80;
+    document.body.append(canvas);
+
+    const ctx = canvas.getContext("2d");
+    loadImage("resources/squares_6.jpg")
+        .then(image => createImageBitmap(image, 80, 0, 160, 160, { imageOrientation: "flipY", resizeWidth:80, resizeHeight:80 }))
+        .then(t.step_func_done(function(imageBitmap) {
+            ctx.drawImage(imageBitmap, 0, 0);
+            checkColors(ctx, canvas.width, canvas.height, 40, 40, [
+                // row, col, r, g, b, a
+                [0, 0, 128, 255, 128, 255],
+                [0, 1, 128, 128, 255, 255],
+                [1, 0, 0, 255, 0, 255],
+                [1, 1, 0, 0, 255, 255],
+            ]);
+        }));
+}, "createImageBitmap with EXIF rotation, imageOrientation flipY, cropping, and resize");
+
+async_test(function(t) {
+    const canvas = document.createElement("canvas");
+    canvas.width = 80;
+    canvas.height = 40;
+    document.body.append(canvas);
+
+    const ctx = canvas.getContext("2d");
+    loadImage("resources/squares_6.jpg")
+        .then(image => createImageBitmap(image, 80, 0, 160, 160, { imageOrientation: "flipY", resizeWidth:80, resizeHeight:40 }))
+        .then(t.step_func_done(function(imageBitmap) {
+            ctx.drawImage(imageBitmap, 0, 0);
+            checkColors(ctx, canvas.width, canvas.height, 40, 20, [
+                // row, col, r, g, b, a
+                [0, 0, 128, 255, 128, 255],
+                [0, 1, 128, 128, 255, 255],
+                [1, 0, 0, 255, 0, 255],
+                [1, 1, 0, 0, 255, 255],
+            ]);
+        }));
+}, "createImageBitmap with EXIF rotation, imageOrientation flipY, cropping, and nonuniform resize");
+
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-record/MediaRecorder-blob-timecode.https.html b/third_party/blink/web_tests/external/wpt/mediacapture-record/MediaRecorder-blob-timecode.https.html
new file mode 100644
index 0000000..cdc63351
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mediacapture-record/MediaRecorder-blob-timecode.https.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+<head>
+  <title>MediaRecorder Blob event timecode</title>
+  <link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#dom-mediarecorder-start">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/resources/testdriver.js"></script>
+  <script src="/resources/testdriver-vendor.js"></script>
+  <script src="../mediacapture-streams/permission-helper.js"></script>
+</head>
+<body>
+
+<script>
+  promise_test(async t => {
+    await setMediaPermission();
+    const stream = await navigator.mediaDevices.getUserMedia({video:true, audio:true});
+    t.add_cleanup(() => stream.getTracks().forEach(tr => tr.stop()));
+    const recorder = new MediaRecorder(stream);
+
+    // Sets 0 timeslice will call the first chunk to be triggered immediately.
+    recorder.start(0);
+    let combinedSize = 0;
+    let previous_timecode = 0;
+    while (combinedSize < 2000) {
+      const {data, timecode} = await new Promise(r => recorder.ondataavailable = r);
+      if (combinedSize === 0) {
+        assert_equals(timecode, 0, "first chunk timecode must be 0");
+      } else {
+        assert_greater_than(timecode, previous_timecode, "timecode must increase monotonically");
+      }
+      previous_timecode = timecode;
+      combinedSize += data.size;
+    }
+    recorder.stop();
+  }, "MediaRecorder Blob timecode for first chunk must be 0 and monotonically increasing");
+
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/fast/dom/remove-children-notification-order-expected.txt b/third_party/blink/web_tests/fast/dom/remove-children-notification-order-expected.txt
index 8033a8c4..e52ec6a 100644
--- a/third_party/blink/web_tests/fast/dom/remove-children-notification-order-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/remove-children-notification-order-expected.txt
@@ -1,3 +1,4 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 PASS
 
 PASS
diff --git a/third_party/blink/web_tests/fast/forms/form-attribute-expected.txt b/third_party/blink/web_tests/fast/forms/form-attribute-expected.txt
index cc8538fb..d182ff4 100644
--- a/third_party/blink/web_tests/fast/forms/form-attribute-expected.txt
+++ b/third_party/blink/web_tests/fast/forms/form-attribute-expected.txt
@@ -1,3 +1,4 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 This test checks the form attribute of the form-associated elements.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
diff --git a/third_party/blink/web_tests/fast/forms/form-element-geometry-expected.txt b/third_party/blink/web_tests/fast/forms/form-element-geometry-expected.txt
new file mode 100644
index 0000000..9fdd8c9
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/form-element-geometry-expected.txt
@@ -0,0 +1 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
diff --git a/third_party/blink/web_tests/fast/forms/misplaced-img-form-registration-expected.txt b/third_party/blink/web_tests/fast/forms/misplaced-img-form-registration-expected.txt
index 69701d5..a9871db 100644
--- a/third_party/blink/web_tests/fast/forms/misplaced-img-form-registration-expected.txt
+++ b/third_party/blink/web_tests/fast/forms/misplaced-img-form-registration-expected.txt
@@ -1,3 +1,4 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 Test for bug 34488: Crash in mangleme in WebCore::Element::getAttribute.
 
 Pass if no crash or assertion failure.
diff --git a/third_party/blink/web_tests/fast/forms/select/customizable-select/button-or-datalist-in-select-usecounter-expected.txt b/third_party/blink/web_tests/fast/forms/select/customizable-select/button-or-datalist-in-select-usecounter-expected.txt
index 22b97e01..2d175d8 100644
--- a/third_party/blink/web_tests/fast/forms/select/customizable-select/button-or-datalist-in-select-usecounter-expected.txt
+++ b/third_party/blink/web_tests/fast/forms/select/customizable-select/button-or-datalist-in-select-usecounter-expected.txt
@@ -1,3 +1,5 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 This is a testharness.js-based test.
 [FAIL] UseCounter is counted when putting button in select.
   assert_true: button in select expected true got false
diff --git a/third_party/blink/web_tests/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt b/third_party/blink/web_tests/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt
new file mode 100644
index 0000000..b543ac2
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt
@@ -0,0 +1,5 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+  
diff --git a/third_party/blink/web_tests/fast/forms/select/customizable-select/disallowed-select-descendants-console-message.html b/third_party/blink/web_tests/fast/forms/select/customizable-select/disallowed-select-descendants-console-message.html
new file mode 100644
index 0000000..9939821b7
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/select/customizable-select/disallowed-select-descendants-console-message.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:ansollan@microsoft.com">
+<link rel=help href="https://issues.chromium.org/issues/347890366">
+
+<style>
+  select, ::picker(select) {
+    appearance: base-select;
+  }
+</style>
+
+<!--Should print one console message. -->
+<select>
+  <label>label</label>
+</select>
+
+<!--Should print two console messages. -->
+<select>
+  <input>
+  <input>
+</select>
+
+<!--Should print one console messages. -->
+<select>
+  <option>
+    <input>
+  </option>
+</select>
+
+<script>
+testRunner.dumpAsText();
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/forms/select/customizable-select/new-structure-in-appearance-auto-expected.txt b/third_party/blink/web_tests/fast/forms/select/customizable-select/new-structure-in-appearance-auto-expected.txt
new file mode 100644
index 0000000..b72f7a7
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/select/customizable-select/new-structure-in-appearance-auto-expected.txt
@@ -0,0 +1,2 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
diff --git a/third_party/blink/web_tests/fast/forms/textarea/textarea-placeholder-relayout-assertion-expected.txt b/third_party/blink/web_tests/fast/forms/textarea/textarea-placeholder-relayout-assertion-expected.txt
index 27a95ef8..d41bc6f7 100644
--- a/third_party/blink/web_tests/fast/forms/textarea/textarea-placeholder-relayout-assertion-expected.txt
+++ b/third_party/blink/web_tests/fast/forms/textarea/textarea-placeholder-relayout-assertion-expected.txt
@@ -1,3 +1,7 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 PASS if not crashed.
 
 >
diff --git a/third_party/blink/web_tests/fast/invalid/residual-style-expected.txt b/third_party/blink/web_tests/fast/invalid/residual-style-expected.txt
new file mode 100644
index 0000000..685b009d
--- /dev/null
+++ b/third_party/blink/web_tests/fast/invalid/residual-style-expected.txt
@@ -0,0 +1,5 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
diff --git a/third_party/blink/web_tests/fast/parser/input-textarea-inside-select-element-expected.txt b/third_party/blink/web_tests/fast/parser/input-textarea-inside-select-element-expected.txt
index d6e879e..763a384 100644
--- a/third_party/blink/web_tests/fast/parser/input-textarea-inside-select-element-expected.txt
+++ b/third_party/blink/web_tests/fast/parser/input-textarea-inside-select-element-expected.txt
@@ -1,4 +1,8 @@
 CONSOLE WARNING: A <select> tag was parsed within another <select> tag and was converted into </select><select>. Please add the missing </select> end tag.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 Test for bug 17421: Lack of end tag of SELECT element causes inaccessible (blank) a rest of a page rendered.
 
 There should be two green bars below.
diff --git a/third_party/blink/web_tests/html5lib/generated/run-menuitem-element-data-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-menuitem-element-data-expected.txt
index 38d19212..da3709d 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-menuitem-element-data-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-menuitem-element-data-expected.txt
@@ -1,3 +1,4 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 ../resources/menuitem-element.dat:
 14
 
diff --git a/third_party/blink/web_tests/html5lib/generated/run-tests1-data-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-tests1-data-expected.txt
index f167640..3e2797bc 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-tests1-data-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-tests1-data-expected.txt
@@ -1,6 +1,10 @@
 CONSOLE ERROR: Uncaught SyntaxError: Unexpected token '<'
 CONSOLE WARNING: A <select> tag was parsed within another <select> tag and was converted into </select><select>. Please add the missing </select> end tag.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 CONSOLE WARNING: A <select> tag was parsed within another <select> tag and was converted into </select><select>. Please add the missing </select> end tag.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 ../resources/tests1.dat:
 30
 100
diff --git a/third_party/blink/web_tests/html5lib/generated/run-tests10-data-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-tests10-data-expected.txt
index afade3f..b199b67 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-tests10-data-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-tests10-data-expected.txt
@@ -1,3 +1,5 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 ../resources/tests10.dat:
 4
 5
diff --git a/third_party/blink/web_tests/html5lib/generated/run-tests18-data-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-tests18-data-expected.txt
index 60478a8..fbededc 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-tests18-data-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-tests18-data-expected.txt
@@ -1,3 +1,5 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 CONSOLE ERROR: Uncaught SyntaxError: Unexpected token '<'
 CONSOLE ERROR: Uncaught SyntaxError: Unexpected token '<'
 CONSOLE ERROR: Uncaught SyntaxError: Unexpected token '<'
diff --git a/third_party/blink/web_tests/html5lib/generated/run-tests7-data-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-tests7-data-expected.txt
index 42fecec..cdd47d7 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-tests7-data-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-tests7-data-expected.txt
@@ -1,5 +1,7 @@
 CONSOLE ERROR: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 CONSOLE WARNING: A <select> tag was parsed within another <select> tag and was converted into </select><select>. Please add the missing </select> end tag.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 ../resources/tests7.dat:
 17
 18
diff --git a/third_party/blink/web_tests/html5lib/generated/run-tests9-data-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-tests9-data-expected.txt
index 63ec8ca6..0586133 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-tests9-data-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-tests9-data-expected.txt
@@ -1,3 +1,5 @@
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
+CONSOLE WARNING: A descendant of a <select> does not follow the content model.
 ../resources/tests9.dat:
 5
 6
diff --git a/third_party/blink/web_tests/platform/linux/fast/inline/initial-letter-inline-kerning-expected.png b/third_party/blink/web_tests/platform/linux/fast/inline/initial-letter-inline-kerning-expected.png
index 91fbc4c..e500c2a 100644
--- a/third_party/blink/web_tests/platform/linux/fast/inline/initial-letter-inline-kerning-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/inline/initial-letter-inline-kerning-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/inline/initial-letter-inline-kerning-expected.png b/third_party/blink/web_tests/platform/mac/fast/inline/initial-letter-inline-kerning-expected.png
index b67b5d1..587c283 100644
--- a/third_party/blink/web_tests/platform/mac/fast/inline/initial-letter-inline-kerning-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/inline/initial-letter-inline-kerning-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/inline/initial-letter-inline-kerning-expected.png b/third_party/blink/web_tests/platform/win/fast/inline/initial-letter-inline-kerning-expected.png
index 210fd75c..ef731fe 100644
--- a/third_party/blink/web_tests/platform/win/fast/inline/initial-letter-inline-kerning-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/inline/initial-letter-inline-kerning-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/customizable-select-disabled/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt b/third_party/blink/web_tests/virtual/customizable-select-disabled/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt
new file mode 100644
index 0000000..b8daa845
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/customizable-select-disabled/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt
@@ -0,0 +1,4 @@
+CONSOLE WARNING: A label tag was parsed inside of a <select> which was not inserted into the document. This is not valid HTML and the behavior may be changed in future versions of chrome.
+CONSOLE WARNING: A input tag was parsed inside of a <select> which caused a </select> to be inserted before this tag. This is not valid HTML and the behavior may be changed in future versions of chrome.
+CONSOLE WARNING: A input tag was parsed inside of a <select> which caused a </select> to be inserted before this tag. This is not valid HTML and the behavior may be changed in future versions of chrome.
+   
diff --git a/third_party/blink/web_tests/virtual/select-parser-relaxation/fast/forms/select/customizable-select/button-or-datalist-in-select-usecounter-expected.txt b/third_party/blink/web_tests/virtual/select-parser-relaxation/fast/forms/select/customizable-select/button-or-datalist-in-select-usecounter-expected.txt
new file mode 100644
index 0000000..22b97e01
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/select-parser-relaxation/fast/forms/select/customizable-select/button-or-datalist-in-select-usecounter-expected.txt
@@ -0,0 +1,11 @@
+This is a testharness.js-based test.
+[FAIL] UseCounter is counted when putting button in select.
+  assert_true: button in select expected true got false
+[FAIL] UseCounter is counted when putting datalist in select.
+  assert_true: datalist in select expected true got false
+[FAIL] UseCounter for <div> in <select>.
+  assert_true: select parser dropped tag expected true got false
+[FAIL] UseCounter is counted when putting input in select.
+  assert_true: expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/select-parser-relaxation/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt b/third_party/blink/web_tests/virtual/select-parser-relaxation/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt
new file mode 100644
index 0000000..1a4baf5
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/select-parser-relaxation/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt
@@ -0,0 +1 @@
+  
diff --git a/third_party/blink/web_tests/virtual/select-parser-relaxation/fast/parser/input-textarea-inside-select-element-expected.txt b/third_party/blink/web_tests/virtual/select-parser-relaxation/fast/parser/input-textarea-inside-select-element-expected.txt
new file mode 100644
index 0000000..d6e879e
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/select-parser-relaxation/fast/parser/input-textarea-inside-select-element-expected.txt
@@ -0,0 +1,4 @@
+CONSOLE WARNING: A <select> tag was parsed within another <select> tag and was converted into </select><select>. Please add the missing </select> end tag.
+Test for bug 17421: Lack of end tag of SELECT element causes inaccessible (blank) a rest of a page rendered.
+
+There should be two green bars below.
diff --git a/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-menuitem-element-data-expected.txt b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-menuitem-element-data-expected.txt
new file mode 100644
index 0000000..38d19212
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-menuitem-element-data-expected.txt
@@ -0,0 +1,18 @@
+../resources/menuitem-element.dat:
+14
+
+Test 14 of 20 in ../resources/menuitem-element.dat failed. Input:
+<!DOCTYPE html><select><menuitem></select>
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <menuitem>
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
diff --git a/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests1-data-expected.txt b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests1-data-expected.txt
new file mode 100644
index 0000000..f167640
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests1-data-expected.txt
@@ -0,0 +1,50 @@
+CONSOLE ERROR: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE WARNING: A <select> tag was parsed within another <select> tag and was converted into </select><select>. Please add the missing </select> end tag.
+CONSOLE WARNING: A <select> tag was parsed within another <select> tag and was converted into </select><select>. Please add the missing </select> end tag.
+../resources/tests1.dat:
+30
+100
+
+Test 30 of 112 in ../resources/tests1.dat failed. Input:
+<select><b><option><select><option></b></select>X
+Got:
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <b>
+|         <option>
+|     <b>
+|     <select>
+|       <b>
+|         <option>
+|     "X"
+Expected:
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <option>
+|     <option>
+|       "X"
+
+Test 100 of 112 in ../resources/tests1.dat failed. Input:
+<select><b><option><select><option></b></select>
+Got:
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <b>
+|         <option>
+|     <b>
+|     <select>
+|       <b>
+|         <option>
+Expected:
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <option>
+|     <option>
diff --git a/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests10-data-expected.txt b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests10-data-expected.txt
new file mode 100644
index 0000000..afade3f
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests10-data-expected.txt
@@ -0,0 +1,103 @@
+../resources/tests10.dat:
+4
+5
+17
+18
+
+Test 4 of 54 in ../resources/tests10.dat failed. Input:
+<!DOCTYPE html><body><select><svg></svg></select>
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <svg svg>
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+
+Test 5 of 54 in ../resources/tests10.dat failed. Input:
+<!DOCTYPE html><body><select><option><svg></svg></option></select>
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <option>
+|         <svg svg>
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <option>
+
+Test 17 of 54 in ../resources/tests10.dat failed. Input:
+<!DOCTYPE html><body><table><tr><td><select><svg><g>foo</g><g>bar</g><p>baz</table><p>quux
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <table>
+|       <tbody>
+|         <tr>
+|           <td>
+|             <select>
+|               <svg svg>
+|                 <svg g>
+|                   "foo"
+|                 <svg g>
+|                   "bar"
+|               <p>
+|                 "baz"
+|     <p>
+|       "quux"
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <table>
+|       <tbody>
+|         <tr>
+|           <td>
+|             <select>
+|               "foobarbaz"
+|     <p>
+|       "quux"
+
+Test 18 of 54 in ../resources/tests10.dat failed. Input:
+<!DOCTYPE html><body><table><select><svg><g>foo</g><g>bar</g><p>baz</table><p>quux
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <svg svg>
+|         <svg g>
+|           "foo"
+|         <svg g>
+|           "bar"
+|       <p>
+|         "baz"
+|     <table>
+|     <p>
+|       "quux"
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       "foobarbaz"
+|     <table>
+|     <p>
+|       "quux"
diff --git a/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests18-data-expected.txt b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests18-data-expected.txt
new file mode 100644
index 0000000..60478a8
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests18-data-expected.txt
@@ -0,0 +1,66 @@
+CONSOLE ERROR: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE ERROR: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE ERROR: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE ERROR: Uncaught SyntaxError: Unexpected token '<'
+../resources/tests18.dat:
+5
+14
+15
+
+Test 5 of 36 in ../resources/tests18.dat failed. Input:
+<!doctype html><html><noscript><plaintext></plaintext>
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|     <noscript>
+|       "<plaintext></plaintext>"
+|   <body>
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|     <noscript>
+|   <body>
+|     <plaintext>
+|       "</plaintext>"
+
+Test 14 of 36 in ../resources/tests18.dat failed. Input:
+<!doctype html><select><plaintext></plaintext>X
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <plaintext>
+|         "</plaintext>X"
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       "X"
+
+Test 15 of 36 in ../resources/tests18.dat failed. Input:
+<!doctype html><table><select><plaintext>a<caption>b
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <plaintext>
+|         "a<caption>b"
+|     <table>
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       "a"
+|     <table>
+|       <caption>
+|         "b"
diff --git a/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests7-data-expected.txt b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests7-data-expected.txt
new file mode 100644
index 0000000..42fecec
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests7-data-expected.txt
@@ -0,0 +1,58 @@
+CONSOLE ERROR: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE WARNING: A <select> tag was parsed within another <select> tag and was converted into </select><select>. Please add the missing </select> end tag.
+../resources/tests7.dat:
+17
+18
+34
+
+Test 17 of 34 in ../resources/tests7.dat failed. Input:
+<!doctype html><select><input>X
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <input>
+|       "X"
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|     <input>
+|     "X"
+
+Test 18 of 34 in ../resources/tests7.dat failed. Input:
+<!doctype html><select><select>X
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|     <select>
+|       "X"
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|     "X"
+
+Test 34 of 34 in ../resources/tests7.dat failed. Input:
+<select><keygen>
+Got:
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <keygen>
+Expected:
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|     <keygen>
diff --git a/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests9-data-expected.txt b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests9-data-expected.txt
new file mode 100644
index 0000000..63ec8ca6
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/select-parser-relaxation/html5lib/generated/run-tests9-data-expected.txt
@@ -0,0 +1,103 @@
+../resources/tests9.dat:
+5
+6
+18
+19
+
+Test 5 of 27 in ../resources/tests9.dat failed. Input:
+<!DOCTYPE html><body><select><math></math></select>
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <math math>
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+
+Test 6 of 27 in ../resources/tests9.dat failed. Input:
+<!DOCTYPE html><body><select><option><math></math></option></select>
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <option>
+|         <math math>
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <option>
+
+Test 18 of 27 in ../resources/tests9.dat failed. Input:
+<!DOCTYPE html><body><table><tr><td><select><math><mi>foo</mi><mi>bar</mi><p>baz</table><p>quux
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <table>
+|       <tbody>
+|         <tr>
+|           <td>
+|             <select>
+|               <math math>
+|                 <math mi>
+|                   "foo"
+|                 <math mi>
+|                   "bar"
+|               <p>
+|                 "baz"
+|     <p>
+|       "quux"
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <table>
+|       <tbody>
+|         <tr>
+|           <td>
+|             <select>
+|               "foobarbaz"
+|     <p>
+|       "quux"
+
+Test 19 of 27 in ../resources/tests9.dat failed. Input:
+<!DOCTYPE html><body><table><select><math><mi>foo</mi><mi>bar</mi><p>baz</table><p>quux
+Got:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       <math math>
+|         <math mi>
+|           "foo"
+|         <math mi>
+|           "bar"
+|       <p>
+|         "baz"
+|     <table>
+|     <p>
+|       "quux"
+Expected:
+| <!DOCTYPE html>
+| <html>
+|   <head>
+|   <body>
+|     <select>
+|       "foobarbaz"
+|     <table>
+|     <p>
+|       "quux"
diff --git a/third_party/boringssl/src b/third_party/boringssl/src
index f8bb652..81345b84 160000
--- a/third_party/boringssl/src
+++ b/third_party/boringssl/src
@@ -1 +1 @@
-Subproject commit f8bb652b01d3b34a20ddbaaa35def260783ee734
+Subproject commit 81345b84505e9c23c156b2c7a1e655a204bd3e9a
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index efc3087..577d77d 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit efc3087b5910f58d031f021f7893034b8a1e18db
+Subproject commit 577d77d5fd18c1e1424568bcdd1b9f24c43a2c10
diff --git a/third_party/crossbench b/third_party/crossbench
index de2d0bb..463368d 160000
--- a/third_party/crossbench
+++ b/third_party/crossbench
@@ -1 +1 @@
-Subproject commit de2d0bbb34999a3266e0c02af50ca51a1480cd4d
+Subproject commit 463368dff43c6f455ecaaf764d8e8f7a96764107
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index 84c1220..60dadf73 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit 84c1220a80b203163a2c3d124ca103f63580d8ce
+Subproject commit 60dadf73f7a0b2353c81d3826b1ee840f57eb0bf
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index b40634d..0f75c87 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit b40634d52b0f5dcde839e73c8bdac4909458217a
+Subproject commit 0f75c875986acc3c538dbc61b9f7aa08d894774e
diff --git a/third_party/ffmpeg b/third_party/ffmpeg
index 30735bb..686d694 160000
--- a/third_party/ffmpeg
+++ b/third_party/ffmpeg
@@ -1 +1 @@
-Subproject commit 30735bb16a66e84d6324b5858eef314822b6d419
+Subproject commit 686d6944501a6ee9c849581e3fe343273d4af3f6
diff --git a/third_party/perfetto b/third_party/perfetto
index 226197a..136de5c 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 226197a61ac2b08b3860b5c73f8411ba0ba43947
+Subproject commit 136de5ccd7163261020064db944bb07bf5f5cf12
diff --git a/third_party/rust/chromium_crates_io/Cargo.lock b/third_party/rust/chromium_crates_io/Cargo.lock
index ea8da12b..2e22284 100644
--- a/third_party/rust/chromium_crates_io/Cargo.lock
+++ b/third_party/rust/chromium_crates_io/Cargo.lock
@@ -609,7 +609,7 @@
 
 [[package]]
 name = "skrifa"
-version = "0.22.0"
+version = "0.22.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bytemuck",
diff --git a/third_party/rust/chromium_crates_io/supply-chain/audits.toml b/third_party/rust/chromium_crates_io/supply-chain/audits.toml
index 0e47a492..8cba5d9 100644
--- a/third_party/rust/chromium_crates_io/supply-chain/audits.toml
+++ b/third_party/rust/chromium_crates_io/supply-chain/audits.toml
@@ -2074,6 +2074,11 @@
 delta = "0.20.0 -> 0.22.0"
 notes = "Changes for adding autohinting support. Crates forbids unsafe code."
 
+[[audits.skrifa]]
+who = "Lukasz Anforowicz <lukasza@chromium.org>"
+criteria = ["safe-to-deploy", "does-not-implement-crypto", "ub-risk-0"]
+delta = "0.22.0 -> 0.22.1"
+
 [[audits.small_ctor]]
 who = "danakj@chromium.org"
 criteria = ["safe-to-run", "does-not-implement-crypto"]
diff --git a/third_party/rust/chromium_crates_io/supply-chain/config.toml b/third_party/rust/chromium_crates_io/supply-chain/config.toml
index fdb4857..8ad0008 100644
--- a/third_party/rust/chromium_crates_io/supply-chain/config.toml
+++ b/third_party/rust/chromium_crates_io/supply-chain/config.toml
@@ -266,7 +266,7 @@
 [policy."simd-adler32:0.3.7"]
 criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
 
-[policy."skrifa:0.22.0"]
+[policy."skrifa:0.22.1"]
 criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
 
 [policy."small_ctor:0.1.2"]
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/.cargo_vcs_info.json b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/.cargo_vcs_info.json
deleted file mode 100644
index e608d74..0000000
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/.cargo_vcs_info.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "git": {
-    "sha1": "6b58785c80e2641a945863301f1f0f270902766c"
-  },
-  "path_in_vcs": "skrifa"
-}
\ No newline at end of file
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/blues.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/blues.rs
deleted file mode 100644
index 7ffad17..0000000
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/blues.rs
+++ /dev/null
@@ -1,711 +0,0 @@
-//! Latin blue values.
-
-use super::super::{
-    super::{unscaled::UnscaledOutlineBuf, OutlineGlyphCollection},
-    cycling::{cycle_backward, cycle_forward},
-    metrics::{UnscaledBlue, UnscaledBlues, MAX_BLUES},
-    style::{blue_flags, ScriptGroup, StyleClass},
-};
-use crate::{charmap::Charmap, FontRef, MetadataProvider};
-use raw::types::F2Dot14;
-use raw::TableProvider;
-
-// Chosen to maximize opportunity to avoid heap allocation while keeping stack
-// size < 2k.
-const MAX_INLINE_POINTS: usize = 256;
-
-// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afblue.h#L73>
-const BLUE_STRING_MAX_LEN: usize = 51;
-
-impl UnscaledBlue {
-    fn is_latin_any_top(&self) -> bool {
-        self.flags & (blue_flags::TOP | blue_flags::LATIN_SUB_TOP) != 0
-    }
-}
-
-/// Compute unscaled blues values for each axis.
-pub(crate) fn compute_unscaled_blues(
-    font: &FontRef,
-    coords: &[F2Dot14],
-    style: &StyleClass,
-) -> [UnscaledBlues; 2] {
-    match style.script.group {
-        ScriptGroup::Default => [
-            // Default group doesn't have horizontal blues
-            Default::default(),
-            compute_default_blues(font, coords, style),
-        ],
-        ScriptGroup::Cjk => compute_cjk_blues(font, coords, style),
-        // Indic group doesn't use blue values (yet?)
-        ScriptGroup::Indic => Default::default(),
-    }
-}
-
-/// Compute unscaled blue values for the default script set.
-///
-/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/aflatin.c#L314>
-fn compute_default_blues(font: &FontRef, coords: &[F2Dot14], style: &StyleClass) -> UnscaledBlues {
-    let mut blues = UnscaledBlues::new();
-    let (mut outline_buf, mut flats, mut rounds) = buffers();
-    let (glyphs, charmap, units_per_em) = things_all_blues_need(font);
-    let flat_threshold = units_per_em / 14;
-    // Walk over each of the blue character sets for our script.
-    for (blue_str, blue_flags) in style.script.blues {
-        let is_top_like = (blue_flags & (blue_flags::TOP | blue_flags::LATIN_SUB_TOP)) != 0;
-        let is_top = blue_flags & blue_flags::TOP != 0;
-        let is_x_height = blue_flags & blue_flags::LATIN_X_HEIGHT != 0;
-        let is_neutral = blue_flags & blue_flags::LATIN_NEUTRAL != 0;
-        let is_long = blue_flags & blue_flags::LATIN_LONG != 0;
-        let mut ascender = i16::MIN;
-        let mut descender = i16::MAX;
-        let mut n_flats = 0;
-        let mut n_rounds = 0;
-        for cluster in blue_str.split(' ') {
-            // TODO shaping: https://github.com/googlefonts/fontations/issues/1128
-            let y_offset = 0;
-            let mut cluster_chars = cluster.chars();
-            let Some(ch) = cluster_chars.next() else {
-                continue;
-            };
-            // Without shaping, we need to skip multi-character clusters
-            // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afshaper.c#L639>
-            if cluster_chars.next().is_some() {
-                continue;
-            }
-            let Some(gid) = charmap.map(ch) else {
-                continue;
-            };
-            if gid.to_u32() == 0 {
-                continue;
-            }
-            let Some(glyph) = glyphs.get(gid) else {
-                continue;
-            };
-            outline_buf.clear();
-            if glyph.draw_unscaled(coords, None, &mut outline_buf).is_err() {
-                continue;
-            }
-            let outline = outline_buf.as_ref();
-            // Reject glyphs that can't produce any rendering
-            if outline.points.len() <= 2 {
-                continue;
-            }
-            let mut best_y: Option<i16> = None;
-            let mut best_y_extremum = if is_top { i32::MIN } else { i32::MAX };
-            let mut best_is_round = false;
-            // Find the extreme point depending on whether this is a top or
-            // bottom blue
-            let best_contour_and_point = if is_top_like {
-                outline.find_last_contour(|point| {
-                    if best_y.is_none() || Some(point.y) > best_y {
-                        best_y = Some(point.y);
-                        ascender = ascender.max(point.y + y_offset);
-                        true
-                    } else {
-                        descender = descender.min(point.y + y_offset);
-                        false
-                    }
-                })
-            } else {
-                outline.find_last_contour(|point| {
-                    if best_y.is_none() || Some(point.y) < best_y {
-                        best_y = Some(point.y);
-                        descender = descender.min(point.y + y_offset);
-                        true
-                    } else {
-                        ascender = ascender.max(point.y + y_offset);
-                        false
-                    }
-                })
-            };
-            let Some((best_contour_range, best_point_ix)) = best_contour_and_point else {
-                continue;
-            };
-            let best_contour = &outline.points[best_contour_range];
-            // If we have a contour and point then best_y is guaranteed to
-            // be Some
-            let mut best_y = best_y.unwrap() as i32;
-            let best_x = best_contour[best_point_ix].x as i32;
-            // Now determine whether the point belongs to a straight or
-            // round segment by examining the previous and next points.
-            let [mut on_point_first, mut on_point_last] =
-                if best_contour[best_point_ix].is_on_curve() {
-                    [Some(best_point_ix); 2]
-                } else {
-                    [None; 2]
-                };
-            let mut segment_first = best_point_ix;
-            let mut segment_last = best_point_ix;
-            // Look for the previous and next points on the contour that
-            // are not on the same Y coordinate, then threshold the
-            // "closeness"
-            for (ix, prev) in cycle_backward(best_contour, best_point_ix) {
-                let dist = (prev.y as i32 - best_y).abs();
-                // Allow a small distance or angle (20 == roughly 2.9 degrees)
-                if dist > 5 && ((prev.x as i32 - best_x).abs() <= (20 * dist)) {
-                    break;
-                }
-                segment_first = ix;
-                if prev.is_on_curve() {
-                    on_point_first = Some(ix);
-                    if on_point_last.is_none() {
-                        on_point_last = Some(ix);
-                    }
-                }
-            }
-            let mut next_ix = 0;
-            for (ix, next) in cycle_forward(best_contour, best_point_ix) {
-                // Save next_ix which is used in "long" blue computation
-                // later
-                next_ix = ix;
-                let dist = (next.y as i32 - best_y).abs();
-                // Allow a small distance or angle (20 == roughly 2.9 degrees)
-                if dist > 5 && ((next.x as i32 - best_x).abs() <= (20 * dist)) {
-                    break;
-                }
-                segment_last = ix;
-                if next.is_on_curve() {
-                    on_point_last = Some(ix);
-                    if on_point_first.is_none() {
-                        on_point_first = Some(ix);
-                    }
-                }
-            }
-            if is_long {
-                // Taken verbatim from FreeType:
-                //
-                // "If this flag is set, we have an additional constraint to
-                // get the blue zone distance: Find a segment of the topmost
-                // (or bottommost) contour that is longer than a heuristic
-                // threshold.  This ensures that small bumps in the outline
-                // are ignored (for example, the `vertical serifs' found in
-                // many Hebrew glyph designs).
-                //
-                // If this segment is long enough, we are done.  Otherwise,
-                // search the segment next to the extremum that is long
-                // enough, has the same direction, and a not too large
-                // vertical distance from the extremum.  Note that the
-                // algorithm doesn't check whether the found segment is
-                // actually the one (vertically) nearest to the extremum.""
-                //
-                // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/aflatin.c#L641>
-                // heuristic threshold value
-                let length_threshold = units_per_em / 25;
-                let dist = (best_contour[segment_last].x as i32
-                    - best_contour[segment_first].x as i32)
-                    .abs();
-                if dist < length_threshold && segment_last - segment_first + 2 <= best_contour.len()
-                {
-                    // heuristic threshold value
-                    let height_threshold = units_per_em / 4;
-                    // find previous point with different x value
-                    let mut prev_ix = best_point_ix;
-                    for (ix, prev) in cycle_backward(best_contour, best_point_ix) {
-                        if prev.x as i32 != best_x {
-                            prev_ix = ix;
-                            break;
-                        }
-                    }
-                    // skip for degenerate case
-                    if prev_ix == best_point_ix {
-                        continue;
-                    }
-                    let is_ltr = (best_contour[prev_ix].x as i32) < best_x;
-                    let mut first = segment_last;
-                    let mut last = first;
-                    let mut p_first = None;
-                    let mut p_last = None;
-                    let mut hit = false;
-                    loop {
-                        if !hit {
-                            // no hit, adjust first point
-                            first = last;
-                            // also adjust first and last on curve point
-                            if best_contour[first].is_on_curve() {
-                                p_first = Some(first);
-                                p_last = Some(first);
-                            } else {
-                                p_first = None;
-                                p_last = None;
-                            }
-                            hit = true;
-                        }
-                        if last < best_contour.len() - 1 {
-                            last += 1;
-                        } else {
-                            last = 0;
-                        }
-                        if (best_y - best_contour[first].y as i32).abs() > height_threshold {
-                            // vertical distance too large
-                            hit = false;
-                            continue;
-                        }
-                        let dist =
-                            (best_contour[last].y as i32 - best_contour[first].y as i32).abs();
-                        if dist > 5
-                            && (best_contour[last].x as i32 - best_contour[first].x as i32).abs()
-                                <= 20 * dist
-                        {
-                            hit = false;
-                            if last == segment_first {
-                                break;
-                            }
-                            continue;
-                        }
-                        if best_contour[last].is_on_curve() {
-                            p_last = Some(last);
-                            if p_first.is_none() {
-                                p_first = Some(last);
-                            }
-                        }
-                        let first_x = best_contour[first].x as i32;
-                        let last_x = best_contour[last].x as i32;
-                        let is_cur_ltr = first_x < last_x;
-                        let dx = (last_x - first_x).abs();
-                        if is_cur_ltr == is_ltr && dx >= length_threshold {
-                            loop {
-                                if last < best_contour.len() - 1 {
-                                    last += 1;
-                                } else {
-                                    last = 0;
-                                }
-                                let dy = (best_contour[last].y as i32
-                                    - best_contour[first].y as i32)
-                                    .abs();
-                                if dy > 5
-                                    && (best_contour[next_ix].x as i32
-                                        - best_contour[first].x as i32)
-                                        .abs()
-                                        <= 20 * dist
-                                {
-                                    if last > 0 {
-                                        last -= 1;
-                                    } else {
-                                        last = best_contour.len() - 1;
-                                    }
-                                    break;
-                                }
-                                p_last = Some(last);
-                                if best_contour[last].is_on_curve() {
-                                    p_last = Some(last);
-                                    if p_first.is_none() {
-                                        p_first = Some(last);
-                                    }
-                                }
-                                if last == segment_first {
-                                    break;
-                                }
-                            }
-                            best_y = best_contour[first].y as i32;
-                            segment_first = first;
-                            segment_last = last;
-                            on_point_first = p_first;
-                            on_point_last = p_last;
-                            break;
-                        }
-                        if last == segment_first {
-                            break;
-                        }
-                    }
-                }
-            }
-            best_y += y_offset as i32;
-            // Is the segment round?
-            // 1. horizontal distance between first and last oncurve point
-            //    is larger than a heuristic flat threshold, then it's flat
-            // 2. either first or last point of segment is offcurve then
-            //    it's round
-            let is_round = match (on_point_first, on_point_last) {
-                (Some(first), Some(last))
-                    if (best_contour[last].x as i32 - best_contour[first].x as i32).abs()
-                        > flat_threshold =>
-                {
-                    false
-                }
-                _ => {
-                    !best_contour[segment_first].is_on_curve()
-                        || !best_contour[segment_last].is_on_curve()
-                }
-            };
-            if is_round && is_neutral {
-                // Ignore round segments for neutral zone
-                continue;
-            }
-            // This seems to ignore LATIN_SUB_TOP?
-            if is_top {
-                if best_y > best_y_extremum {
-                    best_y_extremum = best_y;
-                    best_is_round = is_round;
-                }
-            } else if best_y < best_y_extremum {
-                best_y_extremum = best_y;
-                best_is_round = is_round;
-            }
-            if best_y_extremum != i32::MIN && best_y_extremum != i32::MAX {
-                if best_is_round {
-                    rounds[n_rounds] = best_y_extremum;
-                    n_rounds += 1;
-                } else {
-                    flats[n_flats] = best_y_extremum;
-                    n_flats += 1;
-                }
-            }
-        }
-        if n_flats == 0 && n_rounds == 0 {
-            continue;
-        }
-        rounds[..n_rounds].sort_unstable();
-        flats[..n_flats].sort_unstable();
-        let (mut blue_ref, mut blue_shoot) = if n_flats == 0 {
-            let val = rounds[n_rounds / 2];
-            (val, val)
-        } else if n_rounds == 0 {
-            let val = flats[n_flats / 2];
-            (val, val)
-        } else {
-            (flats[n_flats / 2], rounds[n_rounds / 2])
-        };
-        if blue_shoot != blue_ref {
-            let over_ref = blue_shoot > blue_ref;
-            if is_top_like ^ over_ref {
-                let val = (blue_shoot + blue_ref) / 2;
-                blue_ref = val;
-                blue_shoot = val;
-            }
-        }
-        let mut blue = UnscaledBlue {
-            position: blue_ref,
-            overshoot: blue_shoot,
-            ascender: ascender.into(),
-            descender: descender.into(),
-            flags: blue_flags
-                & (blue_flags::TOP | blue_flags::LATIN_SUB_TOP | blue_flags::LATIN_NEUTRAL),
-        };
-        if is_x_height {
-            blue.flags |= blue_flags::LATIN_BLUE_ADJUSTMENT;
-        }
-        blues.push(blue);
-    }
-    // sort bottoms
-    let mut sorted_indices: [usize; MAX_BLUES] = core::array::from_fn(|ix| ix);
-    let blue_values = blues.as_mut_slice();
-    let len = blue_values.len();
-    if len == 0 {
-        return blues;
-    }
-    // sort from bottom to top
-    for i in 1..len {
-        for j in (1..=i).rev() {
-            let first = &blue_values[sorted_indices[j - 1]];
-            let second = &blue_values[sorted_indices[j]];
-            let a = if first.is_latin_any_top() {
-                first.position
-            } else {
-                first.overshoot
-            };
-            let b = if second.is_latin_any_top() {
-                second.position
-            } else {
-                second.overshoot
-            };
-            if b >= a {
-                break;
-            }
-            sorted_indices.swap(j, j - 1);
-        }
-    }
-    // and adjust tops
-    for i in 0..len - 1 {
-        let index1 = sorted_indices[i];
-        let index2 = sorted_indices[i + 1];
-        let first = &blue_values[index1];
-        let second = &blue_values[index2];
-        let a = if first.is_latin_any_top() {
-            first.overshoot
-        } else {
-            first.position
-        };
-        let b = if second.is_latin_any_top() {
-            second.overshoot
-        } else {
-            second.position
-        };
-        if a > b {
-            if first.is_latin_any_top() {
-                blue_values[index1].overshoot = b;
-            } else {
-                blue_values[index1].position = b;
-            }
-        }
-    }
-    blues
-}
-
-/// Compute unscaled blue values for the CJK script set.
-///
-/// Note: unlike the default code above, this produces two sets of blues,
-/// one for horizontal zones and one for vertical zones, respectively. The
-/// horizontal set is currently not generated because this has been
-/// disabled in FreeType but the code remains because we may want to revisit
-/// in the future.
-///
-/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afcjk.c#L277>
-fn compute_cjk_blues(font: &FontRef, coords: &[F2Dot14], style: &StyleClass) -> [UnscaledBlues; 2] {
-    let mut blues = [UnscaledBlues::new(), UnscaledBlues::new()];
-    let (mut outline_buf, mut flats, mut fills) = buffers();
-    let (glyphs, charmap, _) = things_all_blues_need(font);
-    // Walk over each of the blue character sets for our script.
-    for (blue_str, blue_flags) in style.script.blues {
-        let is_horizontal = blue_flags & blue_flags::CJK_HORIZ != 0;
-        // Note: horizontal blue zones are disabled by default and have been
-        // for many years in FreeType:
-        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afcjk.c#L35>
-        // and <https://gitlab.freedesktop.org/freetype/freetype/-/commit/084abf0469d32a94b1c315bee10f621284694328>
-        if is_horizontal {
-            continue;
-        }
-        let is_right = blue_flags & blue_flags::CJK_RIGHT != 0;
-        let is_top = blue_flags & blue_flags::TOP != 0;
-        let blues = &mut blues[!is_horizontal as usize];
-        if blues.len() >= MAX_BLUES {
-            continue;
-        }
-        let mut n_flats = 0;
-        let mut n_fills = 0;
-        let mut is_fill = true;
-        for cluster in blue_str.split(' ') {
-            // The '|' character is used as a sentinel in the blue string that
-            // signifies a switch to characters that define "flat" values
-            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afcjk.c#L380>
-            if cluster == "|" {
-                is_fill = false;
-                continue;
-            }
-            // TODO shaping: https://github.com/googlefonts/fontations/issues/1128
-            let mut cluster_chars = cluster.chars();
-            let Some(ch) = cluster_chars.next() else {
-                continue;
-            };
-            // Without shaping, we need to skip multi-character clusters
-            // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afshaper.c#L639>
-            if cluster_chars.next().is_some() {
-                continue;
-            }
-            let Some(gid) = charmap.map(ch) else {
-                continue;
-            };
-            if gid.to_u32() == 0 {
-                continue;
-            }
-            let Some(glyph) = glyphs.get(gid) else {
-                continue;
-            };
-            outline_buf.clear();
-            if glyph.draw_unscaled(coords, None, &mut outline_buf).is_err() {
-                continue;
-            }
-            let outline = outline_buf.as_ref();
-            // Reject glyphs that can't produce any rendering
-            if outline.points.len() <= 2 {
-                continue;
-            }
-            // Step right up and find an extrema!
-            // Unwrap is safe because we know per ^ that we have at least 3 points
-            let best_pos = outline
-                .points
-                .iter()
-                .map(|p| if is_horizontal { p.x } else { p.y })
-                .reduce(
-                    if (is_horizontal && is_right) || (!is_horizontal && is_top) {
-                        |a: i16, c: i16| a.max(c)
-                    } else {
-                        |a: i16, c: i16| a.min(c)
-                    },
-                )
-                .unwrap();
-            if is_fill {
-                fills[n_fills] = best_pos;
-                n_fills += 1;
-            } else {
-                flats[n_flats] = best_pos;
-                n_flats += 1;
-            }
-        }
-        if n_flats == 0 && n_fills == 0 {
-            continue;
-        }
-        // Now determine the reference and overshoot of the blue; simply
-        // take the median after a sort
-        fills[..n_fills].sort_unstable();
-        flats[..n_flats].sort_unstable();
-        let (mut blue_ref, mut blue_shoot) = if n_flats == 0 {
-            let value = fills[n_fills / 2] as i32;
-            (value, value)
-        } else if n_fills == 0 {
-            let value = flats[n_flats / 2] as i32;
-            (value, value)
-        } else {
-            (fills[n_fills / 2] as i32, flats[n_flats / 2] as i32)
-        };
-        // Make sure blue_ref >= blue_shoot for top/right or vice versa for
-        // bottom left
-        if blue_shoot != blue_ref {
-            let under_ref = blue_shoot < blue_ref;
-            if is_top ^ under_ref {
-                blue_ref = (blue_shoot + blue_ref) / 2;
-                blue_shoot = blue_ref;
-            }
-        }
-        blues.push(UnscaledBlue {
-            position: blue_ref,
-            overshoot: blue_shoot,
-            ascender: 0,
-            descender: 0,
-            flags: blue_flags & blue_flags::TOP,
-        });
-    }
-    blues
-}
-
-#[inline(always)]
-fn buffers<T: Copy + Default>() -> (
-    UnscaledOutlineBuf<MAX_INLINE_POINTS>,
-    [T; BLUE_STRING_MAX_LEN],
-    [T; BLUE_STRING_MAX_LEN],
-) {
-    (
-        UnscaledOutlineBuf::<MAX_INLINE_POINTS>::new(),
-        [T::default(); BLUE_STRING_MAX_LEN],
-        [T::default(); BLUE_STRING_MAX_LEN],
-    )
-}
-
-/// A thneed is something everyone needs
-#[inline(always)]
-fn things_all_blues_need<'a>(font: &FontRef<'a>) -> (OutlineGlyphCollection<'a>, Charmap<'a>, i32) {
-    (
-        font.outline_glyphs(),
-        font.charmap(),
-        font.head()
-            .map(|head| head.units_per_em())
-            .unwrap_or_default() as i32,
-    )
-}
-
-#[cfg(test)]
-mod tests {
-    use super::{super::super::style, blue_flags, UnscaledBlue};
-    use raw::FontRef;
-
-    #[test]
-    fn latin_blues() {
-        let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
-        let style = &style::STYLE_CLASSES[super::StyleClass::LATN];
-        let blues = super::compute_default_blues(&font, &[], style);
-        let values = blues.as_slice();
-        let expected = [
-            UnscaledBlue {
-                position: 714,
-                overshoot: 725,
-                ascender: 725,
-                descender: -230,
-                flags: blue_flags::TOP,
-            },
-            UnscaledBlue {
-                position: 0,
-                overshoot: -10,
-                ascender: 725,
-                descender: -10,
-                flags: 0,
-            },
-            UnscaledBlue {
-                position: 760,
-                overshoot: 760,
-                ascender: 770,
-                descender: -240,
-                flags: blue_flags::TOP,
-            },
-            UnscaledBlue {
-                position: 536,
-                overshoot: 546,
-                ascender: 546,
-                descender: -10,
-                flags: blue_flags::TOP | blue_flags::LATIN_BLUE_ADJUSTMENT,
-            },
-            UnscaledBlue {
-                position: 0,
-                overshoot: -10,
-                ascender: 546,
-                descender: -10,
-                flags: 0,
-            },
-            UnscaledBlue {
-                position: -240,
-                overshoot: -240,
-                ascender: 760,
-                descender: -240,
-                flags: 0,
-            },
-        ];
-        assert_eq!(values, &expected);
-    }
-
-    #[test]
-    fn hebrew_long_blues() {
-        let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
-        // Hebrew triggers "long" blue code path
-        let style = &style::STYLE_CLASSES[super::StyleClass::HEBR];
-        let blues = super::compute_default_blues(&font, &[], style);
-        let values = blues.as_slice();
-        assert_eq!(values.len(), 3);
-        let expected = [
-            UnscaledBlue {
-                position: 592,
-                overshoot: 592,
-                ascender: 647,
-                descender: -240,
-                flags: blue_flags::TOP,
-            },
-            UnscaledBlue {
-                position: 0,
-                overshoot: -9,
-                ascender: 647,
-                descender: -9,
-                flags: 0,
-            },
-            UnscaledBlue {
-                position: -240,
-                overshoot: -240,
-                ascender: 647,
-                descender: -240,
-                flags: 0,
-            },
-        ];
-        assert_eq!(values, &expected);
-    }
-
-    #[test]
-    fn cjk_blues() {
-        let font = FontRef::new(font_test_data::NOTOSERIFTC_AUTOHINT_METRICS).unwrap();
-        let style = &style::STYLE_CLASSES[super::StyleClass::HANI];
-        let blues = super::compute_cjk_blues(&font, &[], style);
-        let values = blues[1].as_slice();
-        let expected = [
-            UnscaledBlue {
-                position: 837,
-                overshoot: 824,
-                ascender: 0,
-                descender: 0,
-                flags: blue_flags::TOP,
-            },
-            UnscaledBlue {
-                position: -78,
-                overshoot: -66,
-                ascender: 0,
-                descender: 0,
-                flags: 0,
-            },
-        ];
-        assert_eq!(values, &expected);
-    }
-}
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/.cargo-checksum.json b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/.cargo-checksum.json
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/.cargo-checksum.json
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/.cargo-checksum.json
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/.cargo_vcs_info.json b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/.cargo_vcs_info.json
new file mode 100644
index 0000000..250bab8
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "f80507c47a9974863c735af5162d76dba4daf8bf"
+  },
+  "path_in_vcs": "skrifa"
+}
\ No newline at end of file
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/Cargo.toml b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/Cargo.toml
similarity index 92%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/Cargo.toml
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/Cargo.toml
index 85d795a..e3b53ba8 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/Cargo.toml
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/Cargo.toml
@@ -12,7 +12,7 @@
 [package]
 edition = "2021"
 name = "skrifa"
-version = "0.22.0"
+version = "0.22.1"
 build = false
 autobins = false
 autoexamples = false
@@ -43,7 +43,7 @@
 optional = true
 
 [dependencies.read-fonts]
-version = "0.22.0"
+version = "0.22.1"
 default-features = false
 
 [dev-dependencies.kurbo]
@@ -53,7 +53,7 @@
 version = "1.3.0"
 
 [dev-dependencies.read-fonts]
-version = "0.22.0"
+version = "0.22.1"
 features = [
     "scaler_test",
     "serde",
@@ -70,7 +70,11 @@
 version = "0.29.0"
 
 [features]
-default = ["traversal"]
+autohint_shaping = []
+default = [
+    "autohint_shaping",
+    "traversal",
+]
 libm = [
     "dep:core_maths",
     "read-fonts/libm",
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/Cargo.toml.orig b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/Cargo.toml.orig
similarity index 75%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/Cargo.toml.orig
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/Cargo.toml.orig
index 2c2cb9e..95cd4c9 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/Cargo.toml.orig
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/Cargo.toml.orig
@@ -1,6 +1,6 @@
 [package]
 name = "skrifa"
-version = "0.22.0"
+version = "0.22.1"
 description = "Metadata reader and glyph scaler for OpenType fonts."
 readme = "README.md"
 categories = ["text-processing", "parsing", "graphics"]
@@ -15,9 +15,13 @@
 all-features = true
 
 [features]
-default = ["traversal"]
+default = ["autohint_shaping", "traversal"]
 std = ["read-fonts/std"]
 traversal = ["std", "read-fonts/experimental_traverse"]
+# Enables extended shaping support for the autohinter. Enabled by default.
+# This exists as a feature because shaping support is "best effort" and
+# we want the ability to disable it for testing against FreeType.
+autohint_shaping = []
 libm = ["dep:core_maths", "read-fonts/libm"]
 
 [dependencies]
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/LICENSE-APACHE b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/LICENSE-APACHE
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/LICENSE-APACHE
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/LICENSE-APACHE
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/LICENSE-MIT b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/LICENSE-MIT
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/LICENSE-MIT
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/LICENSE-MIT
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/README.md b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/README.md
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/README.md
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/README.md
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/generated/generated_autohint_styles.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/generated/generated_autohint_styles.rs
similarity index 96%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/generated/generated_autohint_styles.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/generated/generated_autohint_styles.rs
index 1d077f2..62782d9 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/generated/generated_autohint_styles.rs
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/generated/generated_autohint_styles.rs
@@ -7,7 +7,7 @@
     ScriptClass {
         name: "Adlam",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"ADLM"),
+        tag: Tag::new(b"Adlm"),
         hint_top_to_bottom: false,
         std_chars: "𞤌 𞤮",
         blues: &[
@@ -20,7 +20,7 @@
     ScriptClass {
         name: "Arabic",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"ARAB"),
+        tag: Tag::new(b"Arab"),
         hint_top_to_bottom: false,
         std_chars: "ل ح ـ",
         blues: &[
@@ -32,7 +32,7 @@
     ScriptClass {
         name: "Armenian",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"ARMN"),
+        tag: Tag::new(b"Armn"),
         hint_top_to_bottom: false,
         std_chars: "ս Ս",
         blues: &[
@@ -47,7 +47,7 @@
     ScriptClass {
         name: "Avestan",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"AVST"),
+        tag: Tag::new(b"Avst"),
         hint_top_to_bottom: false,
         std_chars: "𐬚",
         blues: &[
@@ -58,7 +58,7 @@
     ScriptClass {
         name: "Bamum",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"BAMU"),
+        tag: Tag::new(b"Bamu"),
         hint_top_to_bottom: false,
         std_chars: "ꛁ ꛯ",
         blues: &[
@@ -69,7 +69,7 @@
     ScriptClass {
         name: "Bengali",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"BENG"),
+        tag: Tag::new(b"Beng"),
         hint_top_to_bottom: true,
         std_chars: "০ ৪",
         blues: &[
@@ -82,7 +82,7 @@
     ScriptClass {
         name: "Buhid",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"BUHD"),
+        tag: Tag::new(b"Buhd"),
         hint_top_to_bottom: false,
         std_chars: "ᝋ ᝏ",
         blues: &[
@@ -95,7 +95,7 @@
     ScriptClass {
         name: "Chakma",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"CAKM"),
+        tag: Tag::new(b"Cakm"),
         hint_top_to_bottom: false,
         std_chars: "𑄤 𑄉 𑄛",
         blues: &[
@@ -107,7 +107,7 @@
     ScriptClass {
         name: "Canadian Syllabics",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"CANS"),
+        tag: Tag::new(b"Cans"),
         hint_top_to_bottom: false,
         std_chars: "ᑌ ᓚ",
         blues: &[
@@ -122,7 +122,7 @@
     ScriptClass {
         name: "Carian",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"CARI"),
+        tag: Tag::new(b"Cari"),
         hint_top_to_bottom: false,
         std_chars: "𐊫 𐋉",
         blues: &[
@@ -133,7 +133,7 @@
     ScriptClass {
         name: "Cherokee",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"CHER"),
+        tag: Tag::new(b"Cher"),
         hint_top_to_bottom: false,
         std_chars: "Ꭴ Ꮕ ꮕ",
         blues: &[
@@ -148,7 +148,7 @@
     ScriptClass {
         name: "Coptic",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"COPT"),
+        tag: Tag::new(b"Copt"),
         hint_top_to_bottom: false,
         std_chars: "Ⲟ ⲟ",
         blues: &[
@@ -161,7 +161,7 @@
     ScriptClass {
         name: "Cypriot",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"CPRT"),
+        tag: Tag::new(b"Cprt"),
         hint_top_to_bottom: false,
         std_chars: "𐠅 𐠣",
         blues: &[
@@ -174,7 +174,7 @@
     ScriptClass {
         name: "Cyrillic",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"CYRL"),
+        tag: Tag::new(b"Cyrl"),
         hint_top_to_bottom: false,
         std_chars: "о О",
         blues: &[
@@ -188,7 +188,7 @@
     ScriptClass {
         name: "Devanagari",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"DEVA"),
+        tag: Tag::new(b"Deva"),
         hint_top_to_bottom: true,
         std_chars: "ठ व ट",
         blues: &[
@@ -202,7 +202,7 @@
     ScriptClass {
         name: "Deseret",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"DSRT"),
+        tag: Tag::new(b"Dsrt"),
         hint_top_to_bottom: false,
         std_chars: "𐐄 𐐬",
         blues: &[
@@ -215,7 +215,7 @@
     ScriptClass {
         name: "Ethiopic",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"ETHI"),
+        tag: Tag::new(b"Ethi"),
         hint_top_to_bottom: false,
         std_chars: "ዐ",
         blues: &[
@@ -226,7 +226,7 @@
     ScriptClass {
         name: "Georgian (Mkhedruli)",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"GEOR"),
+        tag: Tag::new(b"Geor"),
         hint_top_to_bottom: false,
         std_chars: "ი ე ა Ჿ",
         blues: &[
@@ -241,7 +241,7 @@
     ScriptClass {
         name: "Georgian (Khutsuri)",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"GEOK"),
+        tag: Tag::new(b"Geok"),
         hint_top_to_bottom: false,
         std_chars: "Ⴖ Ⴑ ⴙ",
         blues: &[
@@ -256,7 +256,7 @@
     ScriptClass {
         name: "Glagolitic",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"GLAG"),
+        tag: Tag::new(b"Glag"),
         hint_top_to_bottom: false,
         std_chars: "Ⱅ ⱅ",
         blues: &[
@@ -269,7 +269,7 @@
     ScriptClass {
         name: "Gothic",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"GOTH"),
+        tag: Tag::new(b"Goth"),
         hint_top_to_bottom: true,
         std_chars: "𐌴 𐌾 𐍃",
         blues: &[
@@ -280,7 +280,7 @@
     ScriptClass {
         name: "Greek",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"GREK"),
+        tag: Tag::new(b"Grek"),
         hint_top_to_bottom: false,
         std_chars: "ο Ο",
         blues: &[
@@ -295,7 +295,7 @@
     ScriptClass {
         name: "Gujarati",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"GUJR"),
+        tag: Tag::new(b"Gujr"),
         hint_top_to_bottom: false,
         std_chars: "ટ ૦",
         blues: &[
@@ -309,7 +309,7 @@
     ScriptClass {
         name: "Gurmukhi",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"GURU"),
+        tag: Tag::new(b"Guru"),
         hint_top_to_bottom: true,
         std_chars: "ਠ ਰ ੦",
         blues: &[
@@ -323,7 +323,7 @@
     ScriptClass {
         name: "Hebrew",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"HEBR"),
+        tag: Tag::new(b"Hebr"),
         hint_top_to_bottom: false,
         std_chars: "ם",
         blues: &[
@@ -335,7 +335,7 @@
     ScriptClass {
         name: "Kayah Li",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"KALI"),
+        tag: Tag::new(b"Kali"),
         hint_top_to_bottom: false,
         std_chars: "ꤍ ꤀",
         blues: &[
@@ -349,7 +349,7 @@
     ScriptClass {
         name: "Khmer",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"KHMR"),
+        tag: Tag::new(b"Khmr"),
         hint_top_to_bottom: false,
         std_chars: "០",
         blues: &[
@@ -363,7 +363,7 @@
     ScriptClass {
         name: "Khmer Symbols",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"KHMS"),
+        tag: Tag::new(b"Khms"),
         hint_top_to_bottom: false,
         std_chars: "᧡ ᧪",
         blues: &[
@@ -374,7 +374,7 @@
     ScriptClass {
         name: "Kannada",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"KNDA"),
+        tag: Tag::new(b"Knda"),
         hint_top_to_bottom: false,
         std_chars: "೦ ಬ",
         blues: &[
@@ -385,7 +385,7 @@
     ScriptClass {
         name: "Lao",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"LAOO"),
+        tag: Tag::new(b"Laoo"),
         hint_top_to_bottom: false,
         std_chars: "໐",
         blues: &[
@@ -399,7 +399,7 @@
     ScriptClass {
         name: "Latin",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"LATN"),
+        tag: Tag::new(b"Latn"),
         hint_top_to_bottom: false,
         std_chars: "o O 0",
         blues: &[
@@ -414,7 +414,7 @@
     ScriptClass {
         name: "Latin Subscript Fallback",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"LATB"),
+        tag: Tag::new(b"Latb"),
         hint_top_to_bottom: false,
         std_chars: "ₒ ₀",
         blues: &[
@@ -429,7 +429,7 @@
     ScriptClass {
         name: "Latin Superscript Fallback",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"LATP"),
+        tag: Tag::new(b"Latp"),
         hint_top_to_bottom: false,
         std_chars: "ᵒ ᴼ ⁰",
         blues: &[
@@ -444,7 +444,7 @@
     ScriptClass {
         name: "Lisu",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"LISU"),
+        tag: Tag::new(b"Lisu"),
         hint_top_to_bottom: false,
         std_chars: "ꓳ",
         blues: &[
@@ -455,7 +455,7 @@
     ScriptClass {
         name: "Malayalam",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"MLYM"),
+        tag: Tag::new(b"Mlym"),
         hint_top_to_bottom: false,
         std_chars: "ഠ റ",
         blues: &[
@@ -466,7 +466,7 @@
     ScriptClass {
         name: "Medefaidrin",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"MEDF"),
+        tag: Tag::new(b"Medf"),
         hint_top_to_bottom: false,
         std_chars: "𖹡 𖹛 𖹯",
         blues: &[
@@ -482,7 +482,7 @@
     ScriptClass {
         name: "Mongolian",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"MONG"),
+        tag: Tag::new(b"Mong"),
         hint_top_to_bottom: true,
         std_chars: "ᡂ ᠪ",
         blues: &[
@@ -493,7 +493,7 @@
     ScriptClass {
         name: "Myanmar",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"MYMR"),
+        tag: Tag::new(b"Mymr"),
         hint_top_to_bottom: false,
         std_chars: "ဝ င ဂ",
         blues: &[
@@ -506,7 +506,7 @@
     ScriptClass {
         name: "N'Ko",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"NKOO"),
+        tag: Tag::new(b"Nkoo"),
         hint_top_to_bottom: false,
         std_chars: "ߋ ߀",
         blues: &[
@@ -519,7 +519,7 @@
     ScriptClass {
         name: "no script",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"NONE"),
+        tag: Tag::new(b"None"),
         hint_top_to_bottom: false,
         std_chars: "",
         blues: &[],
@@ -527,7 +527,7 @@
     ScriptClass {
         name: "Ol Chiki",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"OLCK"),
+        tag: Tag::new(b"Olck"),
         hint_top_to_bottom: false,
         std_chars: "ᱛ",
         blues: &[
@@ -538,7 +538,7 @@
     ScriptClass {
         name: "Old Turkic",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"ORKH"),
+        tag: Tag::new(b"Orkh"),
         hint_top_to_bottom: false,
         std_chars: "𐰗",
         blues: &[
@@ -549,7 +549,7 @@
     ScriptClass {
         name: "Osage",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"OSGE"),
+        tag: Tag::new(b"Osge"),
         hint_top_to_bottom: false,
         std_chars: "𐓂 𐓪",
         blues: &[
@@ -565,7 +565,7 @@
     ScriptClass {
         name: "Osmanya",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"OSMA"),
+        tag: Tag::new(b"Osma"),
         hint_top_to_bottom: false,
         std_chars: "𐒆 𐒠",
         blues: &[
@@ -576,7 +576,7 @@
     ScriptClass {
         name: "Hanifi Rohingya",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"ROHG"),
+        tag: Tag::new(b"Rohg"),
         hint_top_to_bottom: false,
         std_chars: "𐴰",
         blues: &[
@@ -588,7 +588,7 @@
     ScriptClass {
         name: "Saurashtra",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"SAUR"),
+        tag: Tag::new(b"Saur"),
         hint_top_to_bottom: false,
         std_chars: "ꢝ ꣐",
         blues: &[
@@ -599,7 +599,7 @@
     ScriptClass {
         name: "Shavian",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"SHAW"),
+        tag: Tag::new(b"Shaw"),
         hint_top_to_bottom: false,
         std_chars: "𐑴",
         blues: &[
@@ -613,7 +613,7 @@
     ScriptClass {
         name: "Sinhala",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"SINH"),
+        tag: Tag::new(b"Sinh"),
         hint_top_to_bottom: false,
         std_chars: "ට",
         blues: &[
@@ -625,7 +625,7 @@
     ScriptClass {
         name: "Sundanese",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"SUND"),
+        tag: Tag::new(b"Sund"),
         hint_top_to_bottom: false,
         std_chars: "᮰",
         blues: &[
@@ -637,7 +637,7 @@
     ScriptClass {
         name: "Tamil",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"TAML"),
+        tag: Tag::new(b"Taml"),
         hint_top_to_bottom: false,
         std_chars: "௦",
         blues: &[
@@ -648,7 +648,7 @@
     ScriptClass {
         name: "Tai Viet",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"TAVT"),
+        tag: Tag::new(b"Tavt"),
         hint_top_to_bottom: false,
         std_chars: "ꪒ ꪫ",
         blues: &[
@@ -659,7 +659,7 @@
     ScriptClass {
         name: "Telugu",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"TELU"),
+        tag: Tag::new(b"Telu"),
         hint_top_to_bottom: false,
         std_chars: "౦ ౧",
         blues: &[
@@ -670,7 +670,7 @@
     ScriptClass {
         name: "Tifinagh",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"TFNG"),
+        tag: Tag::new(b"Tfng"),
         hint_top_to_bottom: false,
         std_chars: "ⵔ",
         blues: &[
@@ -681,7 +681,7 @@
     ScriptClass {
         name: "Thai",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"THAI"),
+        tag: Tag::new(b"Thai"),
         hint_top_to_bottom: false,
         std_chars: "า ๅ ๐",
         blues: &[
@@ -697,7 +697,7 @@
     ScriptClass {
         name: "Vai",
         group: ScriptGroup::Default,
-        tag: Tag::new(b"VAII"),
+        tag: Tag::new(b"Vaii"),
         hint_top_to_bottom: false,
         std_chars: "ꘓ ꖜ ꖴ",
         blues: &[
@@ -708,7 +708,7 @@
     ScriptClass {
         name: "Limbu",
         group: ScriptGroup::Indic,
-        tag: Tag::new(b"LIMB"),
+        tag: Tag::new(b"Limb"),
         hint_top_to_bottom: false,
         std_chars: "o",
         blues: &[],
@@ -716,7 +716,7 @@
     ScriptClass {
         name: "Oriya",
         group: ScriptGroup::Indic,
-        tag: Tag::new(b"ORYA"),
+        tag: Tag::new(b"Orya"),
         hint_top_to_bottom: false,
         std_chars: "o",
         blues: &[],
@@ -724,7 +724,7 @@
     ScriptClass {
         name: "Syloti Nagri",
         group: ScriptGroup::Indic,
-        tag: Tag::new(b"SYLO"),
+        tag: Tag::new(b"Sylo"),
         hint_top_to_bottom: false,
         std_chars: "o",
         blues: &[],
@@ -732,7 +732,7 @@
     ScriptClass {
         name: "Tibetan",
         group: ScriptGroup::Indic,
-        tag: Tag::new(b"TIBT"),
+        tag: Tag::new(b"Tibt"),
         hint_top_to_bottom: false,
         std_chars: "o",
         blues: &[],
@@ -740,7 +740,7 @@
     ScriptClass {
         name: "CJKV ideographs",
         group: ScriptGroup::Cjk,
-        tag: Tag::new(b"HANI"),
+        tag: Tag::new(b"Hani"),
         hint_top_to_bottom: false,
         std_chars: "田 囗",
         blues: &[
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/scripts/gen_autohint_styles.py b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/scripts/gen_autohint_styles.py
similarity index 99%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/scripts/gen_autohint_styles.py
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/scripts/gen_autohint_styles.py
index 62f1632..24a16d9 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/scripts/gen_autohint_styles.py
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/scripts/gen_autohint_styles.py
@@ -1350,11 +1350,12 @@
             group = "Cjk"
         elif tag in INDIC_GROUP:
             group = "Indic"
+        unicode_tag = tag.lower().capitalize()
         has_features = tag in SCRIPTS_WITH_FEATURES
         buf += "    ScriptClass {\n"
         buf += "        name: \"{}\",\n".format(script["name"])
         buf += "        group: ScriptGroup::{},\n".format(group)
-        buf += "        tag: Tag::new(b\"{}\"),\n".format(tag)
+        buf += "        tag: Tag::new(b\"{}\"),\n".format(unicode_tag)
         buf += "        hint_top_to_bottom: {},\n".format(str(script["hint_top_to_bottom"]).lower())
         # standard characters
         buf += "        std_chars: \"{}\",\n".format(script["std_chars"])
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/attribute.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/attribute.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/attribute.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/attribute.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/charmap.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/charmap.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/charmap.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/charmap.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/collections.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/collections.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/collections.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/collections.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/instance.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/instance.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/instance.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/instance.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/mod.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/mod.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/mod.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/mod.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/transform.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/transform.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/transform.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/transform.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/traversal.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/traversal.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/traversal.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/traversal.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/traversal_tests/mod.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/traversal_tests/mod.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/traversal_tests/mod.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/traversal_tests/mod.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/traversal_tests/test_glyph_defs.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/traversal_tests/test_glyph_defs.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/traversal_tests/test_glyph_defs.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/traversal_tests/test_glyph_defs.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/font.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/font.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/font.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/font.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/instance.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/instance.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/instance.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/instance.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/lib.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/lib.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/lib.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/lib.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/metrics.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/metrics.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/metrics.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/metrics.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/axis.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/axis.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/axis.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/axis.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/cycling.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/cycling.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/cycling.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/cycling.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/hint.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/hint.rs
similarity index 98%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/hint.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/hint.rs
index b151b3d..ab182640 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/hint.rs
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/hint.rs
@@ -359,7 +359,12 @@
 #[cfg(test)]
 mod tests {
     use super::{
-        super::{latin, metrics::Scale, style},
+        super::{
+            latin,
+            metrics::Scale,
+            shape::{Shaper, ShaperMode},
+            style,
+        },
         *,
     };
     use crate::{attribute::Style, MetadataProvider};
@@ -638,11 +643,12 @@
         gid: GlyphId,
         style: &style::StyleClass,
     ) -> (Outline, latin::HintedMetrics) {
+        let shaper = Shaper::new(font, ShaperMode::Nominal);
         let glyphs = font.outline_glyphs();
         let glyph = glyphs.get(gid).unwrap();
         let mut outline = Outline::default();
         outline.fill(&glyph, coords).unwrap();
-        let metrics = latin::compute_unscaled_style_metrics(font, coords, style);
+        let metrics = latin::compute_unscaled_style_metrics(&shaper, coords, style);
         let scale = Scale::new(
             size,
             font.head().unwrap().units_per_em() as i32,
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/instance.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/instance.rs
similarity index 90%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/instance.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/instance.rs
index e123158..5adcb30 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/instance.rs
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/instance.rs
@@ -9,6 +9,7 @@
     },
     metrics::{fixed_mul, pix_round, Scale, UnscaledStyleMetricsSet},
     outline::Outline,
+    shape::{Shaper, ShaperMode},
     style::GlyphStyleMap,
 };
 use alloc::sync::Arc;
@@ -17,6 +18,14 @@
     FontRef, TableProvider,
 };
 
+/// We enable "best effort" mode by default but allow it to be disabled with
+/// a feature for testing.
+const SHAPER_MODE: ShaperMode = if cfg!(feature = "autohint_shaping") {
+    ShaperMode::BestEffort
+} else {
+    ShaperMode::Nominal
+};
+
 /// Set of derived glyph styles that are used for automatic hinting.
 ///
 /// These are invariant per font so can be precomputed and reused for multiple
@@ -33,10 +42,8 @@
                 .maxp()
                 .map(|maxp| maxp.num_glyphs() as u32)
                 .unwrap_or_default();
-            Self(Arc::new(GlyphStyleMap::new(
-                glyph_count,
-                &outlines.font.charmap(),
-            )))
+            let shaper = Shaper::new(&outlines.font, SHAPER_MODE);
+            Self(Arc::new(GlyphStyleMap::new(glyph_count, &shaper)))
         } else {
             Self(Default::default())
         }
@@ -66,10 +73,10 @@
         let metrics = if lazy_metrics {
             UnscaledStyleMetricsSet::lazy(&styles.0)
         } else {
-            UnscaledStyleMetricsSet::precomputed(font, coords, &styles.0)
+            UnscaledStyleMetricsSet::precomputed(font, coords, SHAPER_MODE, &styles.0)
         };
         #[cfg(not(feature = "std"))]
-        let metrics = UnscaledStyleMetricsSet::precomputed(font, coords, &styles.0);
+        let metrics = UnscaledStyleMetricsSet::precomputed(font, coords, SHAPER_MODE, &styles.0);
         let is_fixed_width = font
             .post()
             .map(|post| post.is_fixed_pitch() != 0)
@@ -101,7 +108,7 @@
             .ok_or(DrawError::GlyphNotFound(glyph_id))?;
         let metrics = self
             .metrics
-            .get(&common.font, coords, &self.styles.0, glyph_id)
+            .get(&common.font, coords, SHAPER_MODE, &self.styles.0, glyph_id)
             .ok_or(DrawError::GlyphNotFound(glyph_id))?;
         let units_per_em = glyph.units_per_em() as i32;
         let scale = Scale::new(
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/blues.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/blues.rs
new file mode 100644
index 0000000..2615fe17
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/blues.rs
@@ -0,0 +1,703 @@
+//! Latin blue values.
+
+use super::super::{
+    super::{unscaled::UnscaledOutlineBuf, OutlineGlyphCollection},
+    cycling::{cycle_backward, cycle_forward},
+    metrics::{UnscaledBlue, UnscaledBlues, MAX_BLUES},
+    shape::{ShapedCluster, Shaper},
+    style::{blue_flags, ScriptGroup, StyleClass},
+};
+use crate::{FontRef, MetadataProvider};
+use raw::types::F2Dot14;
+use raw::TableProvider;
+
+// Chosen to maximize opportunity to avoid heap allocation while keeping stack
+// size < 2k.
+const MAX_INLINE_POINTS: usize = 256;
+
+// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afblue.h#L73>
+const BLUE_STRING_MAX_LEN: usize = 51;
+
+impl UnscaledBlue {
+    fn is_latin_any_top(&self) -> bool {
+        self.flags & (blue_flags::TOP | blue_flags::LATIN_SUB_TOP) != 0
+    }
+}
+
+/// Compute unscaled blues values for each axis.
+pub(crate) fn compute_unscaled_blues(
+    shaper: &Shaper,
+    coords: &[F2Dot14],
+    style: &StyleClass,
+) -> [UnscaledBlues; 2] {
+    match style.script.group {
+        ScriptGroup::Default => [
+            // Default group doesn't have horizontal blues
+            Default::default(),
+            compute_default_blues(shaper, coords, style),
+        ],
+        ScriptGroup::Cjk => compute_cjk_blues(shaper, coords, style),
+        // Indic group doesn't use blue values (yet?)
+        ScriptGroup::Indic => Default::default(),
+    }
+}
+
+/// Compute unscaled blue values for the default script set.
+///
+/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/aflatin.c#L314>
+fn compute_default_blues(shaper: &Shaper, coords: &[F2Dot14], style: &StyleClass) -> UnscaledBlues {
+    let mut blues = UnscaledBlues::new();
+    let (mut outline_buf, mut flats, mut rounds) = buffers();
+    let (glyphs, units_per_em) = things_all_blues_need(shaper.font());
+    let flat_threshold = units_per_em / 14;
+    let mut shaped_cluster = ShapedCluster::default();
+    // Walk over each of the blue character sets for our script.
+    for (blue_str, blue_flags) in style.script.blues {
+        let is_top_like = (blue_flags & (blue_flags::TOP | blue_flags::LATIN_SUB_TOP)) != 0;
+        let is_top = blue_flags & blue_flags::TOP != 0;
+        let is_x_height = blue_flags & blue_flags::LATIN_X_HEIGHT != 0;
+        let is_neutral = blue_flags & blue_flags::LATIN_NEUTRAL != 0;
+        let is_long = blue_flags & blue_flags::LATIN_LONG != 0;
+        let mut ascender = i32::MIN;
+        let mut descender = i32::MAX;
+        let mut n_flats = 0;
+        let mut n_rounds = 0;
+        for cluster in blue_str.split(' ') {
+            let mut best_y_extremum = if is_top { i32::MIN } else { i32::MAX };
+            let mut best_is_round = false;
+            shaper.shape_cluster(cluster, &mut shaped_cluster);
+            for (glyph, y_offset) in shaped_cluster
+                .iter()
+                .filter(|g| g.id.to_u32() != 0)
+                .filter_map(|g| Some((glyphs.get(g.id)?, g.y_offset)))
+            {
+                outline_buf.clear();
+                if glyph.draw_unscaled(coords, None, &mut outline_buf).is_err() {
+                    continue;
+                }
+                let outline = outline_buf.as_ref();
+                // Reject glyphs that can't produce any rendering
+                if outline.points.len() <= 2 {
+                    continue;
+                }
+                let mut best_y: Option<i16> = None;
+                // Find the extreme point depending on whether this is a top or
+                // bottom blue
+                let best_contour_and_point = if is_top_like {
+                    outline.find_last_contour(|point| {
+                        if best_y.is_none() || Some(point.y) > best_y {
+                            best_y = Some(point.y);
+                            ascender = ascender.max(point.y as i32 + y_offset);
+                            true
+                        } else {
+                            descender = descender.min(point.y as i32 + y_offset);
+                            false
+                        }
+                    })
+                } else {
+                    outline.find_last_contour(|point| {
+                        if best_y.is_none() || Some(point.y) < best_y {
+                            best_y = Some(point.y);
+                            descender = descender.min(point.y as i32 + y_offset);
+                            true
+                        } else {
+                            ascender = ascender.max(point.y as i32 + y_offset);
+                            false
+                        }
+                    })
+                };
+                let Some((best_contour_range, best_point_ix)) = best_contour_and_point else {
+                    continue;
+                };
+                let best_contour = &outline.points[best_contour_range];
+                // If we have a contour and point then best_y is guaranteed to
+                // be Some
+                let mut best_y = best_y.unwrap() as i32;
+                let best_x = best_contour[best_point_ix].x as i32;
+                // Now determine whether the point belongs to a straight or
+                // round segment by examining the previous and next points.
+                let [mut on_point_first, mut on_point_last] =
+                    if best_contour[best_point_ix].is_on_curve() {
+                        [Some(best_point_ix); 2]
+                    } else {
+                        [None; 2]
+                    };
+                let mut segment_first = best_point_ix;
+                let mut segment_last = best_point_ix;
+                // Look for the previous and next points on the contour that
+                // are not on the same Y coordinate, then threshold the
+                // "closeness"
+                for (ix, prev) in cycle_backward(best_contour, best_point_ix) {
+                    let dist = (prev.y as i32 - best_y).abs();
+                    // Allow a small distance or angle (20 == roughly 2.9 degrees)
+                    if dist > 5 && ((prev.x as i32 - best_x).abs() <= (20 * dist)) {
+                        break;
+                    }
+                    segment_first = ix;
+                    if prev.is_on_curve() {
+                        on_point_first = Some(ix);
+                        if on_point_last.is_none() {
+                            on_point_last = Some(ix);
+                        }
+                    }
+                }
+                let mut next_ix = 0;
+                for (ix, next) in cycle_forward(best_contour, best_point_ix) {
+                    // Save next_ix which is used in "long" blue computation
+                    // later
+                    next_ix = ix;
+                    let dist = (next.y as i32 - best_y).abs();
+                    // Allow a small distance or angle (20 == roughly 2.9 degrees)
+                    if dist > 5 && ((next.x as i32 - best_x).abs() <= (20 * dist)) {
+                        break;
+                    }
+                    segment_last = ix;
+                    if next.is_on_curve() {
+                        on_point_last = Some(ix);
+                        if on_point_first.is_none() {
+                            on_point_first = Some(ix);
+                        }
+                    }
+                }
+                if is_long {
+                    // Taken verbatim from FreeType:
+                    //
+                    // "If this flag is set, we have an additional constraint to
+                    // get the blue zone distance: Find a segment of the topmost
+                    // (or bottommost) contour that is longer than a heuristic
+                    // threshold.  This ensures that small bumps in the outline
+                    // are ignored (for example, the `vertical serifs' found in
+                    // many Hebrew glyph designs).
+                    //
+                    // If this segment is long enough, we are done.  Otherwise,
+                    // search the segment next to the extremum that is long
+                    // enough, has the same direction, and a not too large
+                    // vertical distance from the extremum.  Note that the
+                    // algorithm doesn't check whether the found segment is
+                    // actually the one (vertically) nearest to the extremum.""
+                    //
+                    // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/aflatin.c#L641>
+                    // heuristic threshold value
+                    let length_threshold = units_per_em / 25;
+                    let dist = (best_contour[segment_last].x as i32
+                        - best_contour[segment_first].x as i32)
+                        .abs();
+                    if dist < length_threshold
+                        && segment_last - segment_first + 2 <= best_contour.len()
+                    {
+                        // heuristic threshold value
+                        let height_threshold = units_per_em / 4;
+                        // find previous point with different x value
+                        let mut prev_ix = best_point_ix;
+                        for (ix, prev) in cycle_backward(best_contour, best_point_ix) {
+                            if prev.x as i32 != best_x {
+                                prev_ix = ix;
+                                break;
+                            }
+                        }
+                        // skip for degenerate case
+                        if prev_ix == best_point_ix {
+                            continue;
+                        }
+                        let is_ltr = (best_contour[prev_ix].x as i32) < best_x;
+                        let mut first = segment_last;
+                        let mut last = first;
+                        let mut p_first = None;
+                        let mut p_last = None;
+                        let mut hit = false;
+                        loop {
+                            if !hit {
+                                // no hit, adjust first point
+                                first = last;
+                                // also adjust first and last on curve point
+                                if best_contour[first].is_on_curve() {
+                                    p_first = Some(first);
+                                    p_last = Some(first);
+                                } else {
+                                    p_first = None;
+                                    p_last = None;
+                                }
+                                hit = true;
+                            }
+                            if last < best_contour.len() - 1 {
+                                last += 1;
+                            } else {
+                                last = 0;
+                            }
+                            if (best_y - best_contour[first].y as i32).abs() > height_threshold {
+                                // vertical distance too large
+                                hit = false;
+                                continue;
+                            }
+                            let dist =
+                                (best_contour[last].y as i32 - best_contour[first].y as i32).abs();
+                            if dist > 5
+                                && (best_contour[last].x as i32 - best_contour[first].x as i32)
+                                    .abs()
+                                    <= 20 * dist
+                            {
+                                hit = false;
+                                if last == segment_first {
+                                    break;
+                                }
+                                continue;
+                            }
+                            if best_contour[last].is_on_curve() {
+                                p_last = Some(last);
+                                if p_first.is_none() {
+                                    p_first = Some(last);
+                                }
+                            }
+                            let first_x = best_contour[first].x as i32;
+                            let last_x = best_contour[last].x as i32;
+                            let is_cur_ltr = first_x < last_x;
+                            let dx = (last_x - first_x).abs();
+                            if is_cur_ltr == is_ltr && dx >= length_threshold {
+                                loop {
+                                    if last < best_contour.len() - 1 {
+                                        last += 1;
+                                    } else {
+                                        last = 0;
+                                    }
+                                    let dy = (best_contour[last].y as i32
+                                        - best_contour[first].y as i32)
+                                        .abs();
+                                    if dy > 5
+                                        && (best_contour[next_ix].x as i32
+                                            - best_contour[first].x as i32)
+                                            .abs()
+                                            <= 20 * dist
+                                    {
+                                        if last > 0 {
+                                            last -= 1;
+                                        } else {
+                                            last = best_contour.len() - 1;
+                                        }
+                                        break;
+                                    }
+                                    p_last = Some(last);
+                                    if best_contour[last].is_on_curve() {
+                                        p_last = Some(last);
+                                        if p_first.is_none() {
+                                            p_first = Some(last);
+                                        }
+                                    }
+                                    if last == segment_first {
+                                        break;
+                                    }
+                                }
+                                best_y = best_contour[first].y as i32;
+                                segment_first = first;
+                                segment_last = last;
+                                on_point_first = p_first;
+                                on_point_last = p_last;
+                                break;
+                            }
+                            if last == segment_first {
+                                break;
+                            }
+                        }
+                    }
+                }
+                best_y += y_offset;
+                // Is the segment round?
+                // 1. horizontal distance between first and last oncurve point
+                //    is larger than a heuristic flat threshold, then it's flat
+                // 2. either first or last point of segment is offcurve then
+                //    it's round
+                let is_round = match (on_point_first, on_point_last) {
+                    (Some(first), Some(last))
+                        if (best_contour[last].x as i32 - best_contour[first].x as i32).abs()
+                            > flat_threshold =>
+                    {
+                        false
+                    }
+                    _ => {
+                        !best_contour[segment_first].is_on_curve()
+                            || !best_contour[segment_last].is_on_curve()
+                    }
+                };
+                if is_round && is_neutral {
+                    // Ignore round segments for neutral zone
+                    continue;
+                }
+                // This seems to ignore LATIN_SUB_TOP?
+                if is_top {
+                    if best_y > best_y_extremum {
+                        best_y_extremum = best_y;
+                        best_is_round = is_round;
+                    }
+                } else if best_y < best_y_extremum {
+                    best_y_extremum = best_y;
+                    best_is_round = is_round;
+                }
+            }
+            if best_y_extremum != i32::MIN && best_y_extremum != i32::MAX {
+                if best_is_round {
+                    rounds[n_rounds] = best_y_extremum;
+                    n_rounds += 1;
+                } else {
+                    flats[n_flats] = best_y_extremum;
+                    n_flats += 1;
+                }
+            }
+        }
+        if n_flats == 0 && n_rounds == 0 {
+            continue;
+        }
+        rounds[..n_rounds].sort_unstable();
+        flats[..n_flats].sort_unstable();
+        let (mut blue_ref, mut blue_shoot) = if n_flats == 0 {
+            let val = rounds[n_rounds / 2];
+            (val, val)
+        } else if n_rounds == 0 {
+            let val = flats[n_flats / 2];
+            (val, val)
+        } else {
+            (flats[n_flats / 2], rounds[n_rounds / 2])
+        };
+        if blue_shoot != blue_ref {
+            let over_ref = blue_shoot > blue_ref;
+            if is_top_like ^ over_ref {
+                let val = (blue_shoot + blue_ref) / 2;
+                blue_ref = val;
+                blue_shoot = val;
+            }
+        }
+        let mut blue = UnscaledBlue {
+            position: blue_ref,
+            overshoot: blue_shoot,
+            ascender,
+            descender,
+            flags: blue_flags
+                & (blue_flags::TOP | blue_flags::LATIN_SUB_TOP | blue_flags::LATIN_NEUTRAL),
+        };
+        if is_x_height {
+            blue.flags |= blue_flags::LATIN_BLUE_ADJUSTMENT;
+        }
+        blues.push(blue);
+    }
+    // sort bottoms
+    let mut sorted_indices: [usize; MAX_BLUES] = core::array::from_fn(|ix| ix);
+    let blue_values = blues.as_mut_slice();
+    let len = blue_values.len();
+    if len == 0 {
+        return blues;
+    }
+    // sort from bottom to top
+    for i in 1..len {
+        for j in (1..=i).rev() {
+            let first = &blue_values[sorted_indices[j - 1]];
+            let second = &blue_values[sorted_indices[j]];
+            let a = if first.is_latin_any_top() {
+                first.position
+            } else {
+                first.overshoot
+            };
+            let b = if second.is_latin_any_top() {
+                second.position
+            } else {
+                second.overshoot
+            };
+            if b >= a {
+                break;
+            }
+            sorted_indices.swap(j, j - 1);
+        }
+    }
+    // and adjust tops
+    for i in 0..len - 1 {
+        let index1 = sorted_indices[i];
+        let index2 = sorted_indices[i + 1];
+        let first = &blue_values[index1];
+        let second = &blue_values[index2];
+        let a = if first.is_latin_any_top() {
+            first.overshoot
+        } else {
+            first.position
+        };
+        let b = if second.is_latin_any_top() {
+            second.overshoot
+        } else {
+            second.position
+        };
+        if a > b {
+            if first.is_latin_any_top() {
+                blue_values[index1].overshoot = b;
+            } else {
+                blue_values[index1].position = b;
+            }
+        }
+    }
+    blues
+}
+
+/// Compute unscaled blue values for the CJK script set.
+///
+/// Note: unlike the default code above, this produces two sets of blues,
+/// one for horizontal zones and one for vertical zones, respectively. The
+/// horizontal set is currently not generated because this has been
+/// disabled in FreeType but the code remains because we may want to revisit
+/// in the future.
+///
+/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afcjk.c#L277>
+fn compute_cjk_blues(
+    shaper: &Shaper,
+    coords: &[F2Dot14],
+    style: &StyleClass,
+) -> [UnscaledBlues; 2] {
+    let mut blues = [UnscaledBlues::new(), UnscaledBlues::new()];
+    let (mut outline_buf, mut flats, mut fills) = buffers();
+    let (glyphs, _) = things_all_blues_need(shaper.font());
+    let mut shaped_cluster = ShapedCluster::default();
+    // Walk over each of the blue character sets for our script.
+    for (blue_str, blue_flags) in style.script.blues {
+        let is_horizontal = blue_flags & blue_flags::CJK_HORIZ != 0;
+        // Note: horizontal blue zones are disabled by default and have been
+        // for many years in FreeType:
+        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afcjk.c#L35>
+        // and <https://gitlab.freedesktop.org/freetype/freetype/-/commit/084abf0469d32a94b1c315bee10f621284694328>
+        if is_horizontal {
+            continue;
+        }
+        let is_right = blue_flags & blue_flags::CJK_RIGHT != 0;
+        let is_top = blue_flags & blue_flags::TOP != 0;
+        let blues = &mut blues[!is_horizontal as usize];
+        if blues.len() >= MAX_BLUES {
+            continue;
+        }
+        let mut n_flats = 0;
+        let mut n_fills = 0;
+        let mut is_fill = true;
+        for cluster in blue_str.split(' ') {
+            // The '|' character is used as a sentinel in the blue string that
+            // signifies a switch to characters that define "flat" values
+            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afcjk.c#L380>
+            if cluster == "|" {
+                is_fill = false;
+                continue;
+            }
+            shaper.shape_cluster(cluster, &mut shaped_cluster);
+            for glyph in shaped_cluster
+                .iter()
+                .filter(|g| g.id.to_u32() != 0)
+                .filter_map(|g| glyphs.get(g.id))
+            {
+                outline_buf.clear();
+                if glyph.draw_unscaled(coords, None, &mut outline_buf).is_err() {
+                    continue;
+                }
+                let outline = outline_buf.as_ref();
+                // Reject glyphs that can't produce any rendering
+                if outline.points.len() <= 2 {
+                    continue;
+                }
+                // Step right up and find an extrema!
+                // Unwrap is safe because we know per ^ that we have at least 3 points
+                let best_pos = outline
+                    .points
+                    .iter()
+                    .map(|p| if is_horizontal { p.x } else { p.y })
+                    .reduce(
+                        if (is_horizontal && is_right) || (!is_horizontal && is_top) {
+                            |a: i16, c: i16| a.max(c)
+                        } else {
+                            |a: i16, c: i16| a.min(c)
+                        },
+                    )
+                    .unwrap();
+                if is_fill {
+                    fills[n_fills] = best_pos;
+                    n_fills += 1;
+                } else {
+                    flats[n_flats] = best_pos;
+                    n_flats += 1;
+                }
+            }
+        }
+        if n_flats == 0 && n_fills == 0 {
+            continue;
+        }
+        // Now determine the reference and overshoot of the blue; simply
+        // take the median after a sort
+        fills[..n_fills].sort_unstable();
+        flats[..n_flats].sort_unstable();
+        let (mut blue_ref, mut blue_shoot) = if n_flats == 0 {
+            let value = fills[n_fills / 2] as i32;
+            (value, value)
+        } else if n_fills == 0 {
+            let value = flats[n_flats / 2] as i32;
+            (value, value)
+        } else {
+            (fills[n_fills / 2] as i32, flats[n_flats / 2] as i32)
+        };
+        // Make sure blue_ref >= blue_shoot for top/right or vice versa for
+        // bottom left
+        if blue_shoot != blue_ref {
+            let under_ref = blue_shoot < blue_ref;
+            if is_top ^ under_ref {
+                blue_ref = (blue_shoot + blue_ref) / 2;
+                blue_shoot = blue_ref;
+            }
+        }
+        blues.push(UnscaledBlue {
+            position: blue_ref,
+            overshoot: blue_shoot,
+            ascender: 0,
+            descender: 0,
+            flags: blue_flags & blue_flags::TOP,
+        });
+    }
+    blues
+}
+
+#[inline(always)]
+fn buffers<T: Copy + Default>() -> (
+    UnscaledOutlineBuf<MAX_INLINE_POINTS>,
+    [T; BLUE_STRING_MAX_LEN],
+    [T; BLUE_STRING_MAX_LEN],
+) {
+    (
+        UnscaledOutlineBuf::<MAX_INLINE_POINTS>::new(),
+        [T::default(); BLUE_STRING_MAX_LEN],
+        [T::default(); BLUE_STRING_MAX_LEN],
+    )
+}
+
+/// A thneed is something everyone needs
+#[inline(always)]
+fn things_all_blues_need<'a>(font: &FontRef<'a>) -> (OutlineGlyphCollection<'a>, i32) {
+    (
+        font.outline_glyphs(),
+        font.head()
+            .map(|head| head.units_per_em())
+            .unwrap_or_default() as i32,
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{
+        super::super::{
+            shape::{Shaper, ShaperMode},
+            style,
+        },
+        blue_flags, UnscaledBlue,
+    };
+    use raw::FontRef;
+
+    #[test]
+    fn latin_blues() {
+        let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
+        let shaper = Shaper::new(&font, ShaperMode::Nominal);
+        let style = &style::STYLE_CLASSES[super::StyleClass::LATN];
+        let blues = super::compute_default_blues(&shaper, &[], style);
+        let values = blues.as_slice();
+        let expected = [
+            UnscaledBlue {
+                position: 714,
+                overshoot: 725,
+                ascender: 725,
+                descender: -230,
+                flags: blue_flags::TOP,
+            },
+            UnscaledBlue {
+                position: 0,
+                overshoot: -10,
+                ascender: 725,
+                descender: -10,
+                flags: 0,
+            },
+            UnscaledBlue {
+                position: 760,
+                overshoot: 760,
+                ascender: 770,
+                descender: -240,
+                flags: blue_flags::TOP,
+            },
+            UnscaledBlue {
+                position: 536,
+                overshoot: 546,
+                ascender: 546,
+                descender: -10,
+                flags: blue_flags::TOP | blue_flags::LATIN_BLUE_ADJUSTMENT,
+            },
+            UnscaledBlue {
+                position: 0,
+                overshoot: -10,
+                ascender: 546,
+                descender: -10,
+                flags: 0,
+            },
+            UnscaledBlue {
+                position: -240,
+                overshoot: -240,
+                ascender: 760,
+                descender: -240,
+                flags: 0,
+            },
+        ];
+        assert_eq!(values, &expected);
+    }
+
+    #[test]
+    fn hebrew_long_blues() {
+        let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
+        let shaper = Shaper::new(&font, ShaperMode::Nominal);
+        // Hebrew triggers "long" blue code path
+        let style = &style::STYLE_CLASSES[super::StyleClass::HEBR];
+        let blues = super::compute_default_blues(&shaper, &[], style);
+        let values = blues.as_slice();
+        assert_eq!(values.len(), 3);
+        let expected = [
+            UnscaledBlue {
+                position: 592,
+                overshoot: 592,
+                ascender: 647,
+                descender: -240,
+                flags: blue_flags::TOP,
+            },
+            UnscaledBlue {
+                position: 0,
+                overshoot: -9,
+                ascender: 647,
+                descender: -9,
+                flags: 0,
+            },
+            UnscaledBlue {
+                position: -240,
+                overshoot: -240,
+                ascender: 647,
+                descender: -240,
+                flags: 0,
+            },
+        ];
+        assert_eq!(values, &expected);
+    }
+
+    #[test]
+    fn cjk_blues() {
+        let font = FontRef::new(font_test_data::NOTOSERIFTC_AUTOHINT_METRICS).unwrap();
+        let shaper = Shaper::new(&font, ShaperMode::Nominal);
+        let style = &style::STYLE_CLASSES[super::StyleClass::HANI];
+        let blues = super::compute_cjk_blues(&shaper, &[], style);
+        let values = blues[1].as_slice();
+        let expected = [
+            UnscaledBlue {
+                position: 837,
+                overshoot: 824,
+                ascender: 0,
+                descender: 0,
+                flags: blue_flags::TOP,
+            },
+            UnscaledBlue {
+                position: -78,
+                overshoot: -66,
+                ascender: 0,
+                descender: 0,
+                flags: 0,
+            },
+        ];
+        assert_eq!(values, &expected);
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/edges.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/edges.rs
similarity index 99%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/edges.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/edges.rs
index 15d12f76..3eb0bcc 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/edges.rs
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/edges.rs
@@ -378,6 +378,7 @@
             latin,
             metrics::{self, ScaledWidth},
             outline::Outline,
+            shape::{Shaper, ShaperMode},
             style,
         },
         *,
@@ -770,9 +771,10 @@
         expected_v_edges: &[Edge],
     ) {
         let font = FontRef::new(font_data).unwrap();
+        let shaper = Shaper::new(&font, ShaperMode::Nominal);
         let class = &style::STYLE_CLASSES[style_class];
         let unscaled_metrics =
-            latin::metrics::compute_unscaled_style_metrics(&font, Default::default(), class);
+            latin::metrics::compute_unscaled_style_metrics(&shaper, Default::default(), class);
         let scale = metrics::Scale::new(
             16.0,
             font.head().unwrap().units_per_em() as i32,
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/hint.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/hint.rs
similarity index 99%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/hint.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/hint.rs
index 96893c77..b2b9c71 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/hint.rs
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/hint.rs
@@ -808,6 +808,7 @@
             latin,
             metrics::{self},
             outline::Outline,
+            shape::{Shaper, ShaperMode},
             style,
         },
         *,
@@ -879,9 +880,10 @@
         expected_v_edges: &[(i32, u8)],
     ) {
         let font = FontRef::new(font_data).unwrap();
+        let shaper = Shaper::new(&font, ShaperMode::Nominal);
         let class = &style::STYLE_CLASSES[class];
         let unscaled_metrics =
-            latin::metrics::compute_unscaled_style_metrics(&font, Default::default(), class);
+            latin::metrics::compute_unscaled_style_metrics(&shaper, Default::default(), class);
         let scale = metrics::Scale::new(
             16.0,
             font.head().unwrap().units_per_em() as i32,
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/metrics.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/metrics.rs
similarity index 96%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/metrics.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/metrics.rs
index c53beff..cf13967f 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/metrics.rs
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/metrics.rs
@@ -13,20 +13,21 @@
         ScaledStyleMetrics, ScaledWidth, UnscaledAxisMetrics, UnscaledBlue, UnscaledStyleMetrics,
         WidthMetrics,
     },
+    shape::Shaper,
     style::{blue_flags, ScriptGroup, StyleClass},
 };
 use crate::{prelude::Size, MetadataProvider};
-use raw::{types::F2Dot14, FontRef};
+use raw::types::F2Dot14;
 
 /// Computes unscaled metrics for the Latin writing system.
 ///
 /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/aflatin.c#L1134>
 pub(crate) fn compute_unscaled_style_metrics(
-    font: &FontRef,
+    shaper: &Shaper,
     coords: &[F2Dot14],
     style: &StyleClass,
 ) -> UnscaledStyleMetrics {
-    let charmap = font.charmap();
+    let charmap = shaper.charmap();
     // We don't attempt to produce any metrics if we don't have a Unicode
     // cmap
     // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/aflatin.c#L1146>
@@ -46,9 +47,9 @@
             ..Default::default()
         };
     }
-    let [hwidths, vwidths] = super::widths::compute_widths(font, coords, style.script);
-    let [hblues, vblues] = super::blues::compute_unscaled_blues(font, coords, style);
-    let glyph_metrics = font.glyph_metrics(Size::unscaled(), coords);
+    let [hwidths, vwidths] = super::widths::compute_widths(shaper, coords, style.script);
+    let [hblues, vblues] = super::blues::compute_unscaled_blues(shaper, coords, style);
+    let glyph_metrics = shaper.font().glyph_metrics(Size::unscaled(), coords);
     let mut digit_advance = None;
     let mut digits_have_same_width = true;
     for ch in '0'..='9' {
@@ -302,7 +303,10 @@
 
 #[cfg(test)]
 mod tests {
-    use super::{super::super::style, *};
+    use super::{
+        super::super::{shape::ShaperMode, style},
+        *,
+    };
     use crate::attribute::Style;
     use raw::{FontRef, TableProvider};
 
@@ -380,7 +384,8 @@
     fn make_scaled_metrics(font_data: &[u8], style_class: usize) -> ScaledStyleMetrics {
         let font = FontRef::new(font_data).unwrap();
         let class = &style::STYLE_CLASSES[style_class];
-        let unscaled_metrics = compute_unscaled_style_metrics(&font, Default::default(), class);
+        let shaper = Shaper::new(&font, ShaperMode::Nominal);
+        let unscaled_metrics = compute_unscaled_style_metrics(&shaper, Default::default(), class);
         let scale = Scale::new(
             16.0,
             font.head().unwrap().units_per_em() as i32,
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/mod.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/mod.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/mod.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/mod.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/segments.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/segments.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/segments.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/segments.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/widths.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/widths.rs
similarity index 87%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/widths.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/widths.rs
index 3e65a30..10633dc 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/widths.rs
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/widths.rs
@@ -4,10 +4,11 @@
     axis::Axis,
     metrics::{self, UnscaledWidths, WidthMetrics, MAX_WIDTHS},
     outline::Outline,
+    shape::{ShapedCluster, Shaper},
     style::{ScriptClass, ScriptGroup},
 };
 use crate::MetadataProvider;
-use raw::{types::F2Dot14, FontRef, TableProvider};
+use raw::{types::F2Dot14, TableProvider};
 
 /// Compute all stem widths and initialize standard width and height for the
 /// given script.
@@ -16,12 +17,12 @@
 ///
 /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/aflatin.c#L54>
 pub(super) fn compute_widths(
-    font: &FontRef,
+    shaper: &Shaper,
     coords: &[F2Dot14],
     script: &ScriptClass,
 ) -> [(WidthMetrics, UnscaledWidths); 2] {
     let mut result: [(WidthMetrics, UnscaledWidths); 2] = Default::default();
-    let charmap = font.charmap();
+    let font = shaper.font();
     let glyphs = font.outline_glyphs();
     let units_per_em = font
         .head()
@@ -29,16 +30,22 @@
         .unwrap_or_default();
     let mut outline = Outline::default();
     let mut axis = Axis::default();
+    let mut shaped_cluster = ShapedCluster::default();
     // We take the first available glyph from the standard character set.
-    if let Some(glyph) = script
+    let glyph = script
         .std_chars
         .split(' ')
-        .map(|cluster| cluster.chars())
-        .filter_map(|mut chars| Some((chars.next()?, chars.count())))
-        .filter(|(_ch, remaining_count)| *remaining_count == 0)
-        .filter_map(|(ch, _)| glyphs.get(charmap.map(ch)?))
-        .next()
-    {
+        .filter_map(|cluster| {
+            shaper.shape_cluster(cluster, &mut shaped_cluster);
+            // Reject input that maps to more than a single glyph
+            // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/aflatin.c#L128>
+            match shaped_cluster.as_slice() {
+                [glyph] if glyph.id.to_u32() != 0 => glyphs.get(glyph.id),
+                _ => None,
+            }
+        })
+        .next();
+    if let Some(glyph) = glyph {
         if outline.fill(&glyph, coords).is_ok() && !outline.points.is_empty() {
             // Now process each dimension
             for (dim, (_metrics, widths)) in result.iter_mut().enumerate() {
@@ -91,7 +98,10 @@
 
 #[cfg(test)]
 mod tests {
-    use super::{super::super::style, *};
+    use super::{
+        super::super::{shape::ShaperMode, style},
+        *,
+    };
     use raw::FontRef;
 
     #[test]
@@ -180,9 +190,10 @@
 
     fn check_widths(font_data: &[u8], script_class: usize, expected: [(WidthMetrics, &[i32]); 2]) {
         let font = FontRef::new(font_data).unwrap();
+        let shaper = Shaper::new(&font, ShaperMode::Nominal);
         let script = &style::SCRIPT_CLASSES[script_class];
         let [(hori_metrics, hori_widths), (vert_metrics, vert_widths)] =
-            compute_widths(&font, Default::default(), script);
+            compute_widths(&shaper, Default::default(), script);
         assert_eq!(hori_metrics, expected[0].0);
         assert_eq!(hori_widths.as_slice(), expected[0].1);
         assert_eq!(vert_metrics, expected[1].0);
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/metrics.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/metrics.rs
similarity index 91%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/metrics.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/metrics.rs
index 275ef1ab..354b89ee 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/metrics.rs
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/metrics.rs
@@ -3,6 +3,7 @@
 use super::{
     super::Target,
     axis::Dimension,
+    shape::{Shaper, ShaperMode},
     style::{GlyphStyleMap, ScriptGroup, StyleClass},
 };
 use crate::{attribute::Style, collections::SmallVec, FontRef};
@@ -92,15 +93,21 @@
 impl UnscaledStyleMetricsSet {
     /// Creates a precomputed style metrics set containing all metrics
     /// required by the glyph map.
-    pub fn precomputed(font: &FontRef, coords: &[F2Dot14], style_map: &GlyphStyleMap) -> Self {
+    pub fn precomputed(
+        font: &FontRef,
+        coords: &[F2Dot14],
+        shaper_mode: ShaperMode,
+        style_map: &GlyphStyleMap,
+    ) -> Self {
         // The metrics_styles() iterator does not report exact size so we
         // preallocate and extend here rather than collect to avoid
         // over allocating memory.
+        let shaper = Shaper::new(font, shaper_mode);
         let mut vec = Vec::with_capacity(style_map.metrics_count());
         vec.extend(
             style_map
                 .metrics_styles()
-                .map(|style| super::latin::compute_unscaled_style_metrics(font, coords, style)),
+                .map(|style| super::latin::compute_unscaled_style_metrics(&shaper, coords, style)),
         );
         Self::Precomputed(vec)
     }
@@ -119,6 +126,7 @@
         &self,
         font: &FontRef,
         coords: &[F2Dot14],
+        shaper_mode: ShaperMode,
         style_map: &GlyphStyleMap,
         glyph_id: GlyphId,
     ) -> Option<UnscaledStyleMetrics> {
@@ -137,9 +145,10 @@
                 // The std RwLock doesn't support upgrading and contention is
                 // expected to be low, so let's just race to compute the new
                 // metrics.
+                let shaper = Shaper::new(font, shaper_mode);
                 let style_class = style.style_class()?;
                 let metrics =
-                    super::latin::compute_unscaled_style_metrics(font, coords, style_class);
+                    super::latin::compute_unscaled_style_metrics(&shaper, coords, style_class);
                 let mut entry = lazy.write().unwrap();
                 *entry.get_mut(index)? = Some(metrics.clone());
                 Some(metrics)
@@ -377,8 +386,13 @@
 
 #[cfg(test)]
 mod tests {
-    use super::{super::style::STYLE_CLASSES, *};
-    use crate::MetadataProvider;
+    use super::{
+        super::{
+            shape::{Shaper, ShaperMode},
+            style::STYLE_CLASSES,
+        },
+        *,
+    };
     use raw::TableProvider;
 
     #[test]
@@ -404,9 +418,11 @@
     fn precomputed_style_set() {
         let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
         let coords = &[];
+        let shaper = Shaper::new(&font, ShaperMode::Nominal);
         let glyph_count = font.maxp().unwrap().num_glyphs() as u32;
-        let style_map = GlyphStyleMap::new(glyph_count, &font.charmap());
-        let style_set = UnscaledStyleMetricsSet::precomputed(&font, coords, &style_map);
+        let style_map = GlyphStyleMap::new(glyph_count, &shaper);
+        let style_set =
+            UnscaledStyleMetricsSet::precomputed(&font, coords, ShaperMode::Nominal, &style_map);
         let UnscaledStyleMetricsSet::Precomputed(set) = &style_set else {
             panic!("we definitely made a precomputed style set");
         };
@@ -424,15 +440,22 @@
     fn lazy_style_set() {
         let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
         let coords = &[];
+        let shaper = Shaper::new(&font, ShaperMode::Nominal);
         let glyph_count = font.maxp().unwrap().num_glyphs() as u32;
-        let style_map = GlyphStyleMap::new(glyph_count, &font.charmap());
+        let style_map = GlyphStyleMap::new(glyph_count, &shaper);
         let style_set = UnscaledStyleMetricsSet::lazy(&style_map);
         let all_empty = lazy_set_presence(&style_set);
         // Set starts out all empty
         assert_eq!(all_empty, [false; 3]);
         // First load a CJK glyph
         let metrics2 = style_set
-            .get(&font, coords, &style_map, GlyphId::new(0))
+            .get(
+                &font,
+                coords,
+                ShaperMode::Nominal,
+                &style_map,
+                GlyphId::new(0),
+            )
             .unwrap();
         assert_eq!(
             STYLE_CLASSES[metrics2.class_ix as usize].name,
@@ -442,14 +465,26 @@
         assert_eq!(only_cjk, [false, false, true]);
         // Then a Hebrew glyph
         let metrics1 = style_set
-            .get(&font, coords, &style_map, GlyphId::new(1))
+            .get(
+                &font,
+                coords,
+                ShaperMode::Nominal,
+                &style_map,
+                GlyphId::new(1),
+            )
             .unwrap();
         assert_eq!(STYLE_CLASSES[metrics1.class_ix as usize].name, "Hebrew");
         let hebrew_and_cjk = lazy_set_presence(&style_set);
         assert_eq!(hebrew_and_cjk, [false, true, true]);
         // And finally a Latin glyph
         let metrics0 = style_set
-            .get(&font, coords, &style_map, GlyphId::new(15))
+            .get(
+                &font,
+                coords,
+                ShaperMode::Nominal,
+                &style_map,
+                GlyphId::new(15),
+            )
             .unwrap();
         assert_eq!(STYLE_CLASSES[metrics0.class_ix as usize].name, "Latin");
         let all_present = lazy_set_presence(&style_set);
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/mod.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/mod.rs
similarity index 94%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/mod.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/mod.rs
index 8b760f63..556ec71 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/mod.rs
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/mod.rs
@@ -7,6 +7,7 @@
 mod latin;
 mod metrics;
 mod outline;
+mod shape;
 mod style;
 
 pub use instance::GlyphStyles;
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/outline.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/outline.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/outline.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/outline.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/shape.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/shape.rs
new file mode 100644
index 0000000..4723189
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/shape.rs
@@ -0,0 +1,515 @@
+//! Shaping support for autohinting.
+
+use super::style::{GlyphStyle, StyleClass};
+use crate::{charmap::Charmap, collections::SmallVec, FontRef, GlyphId, MetadataProvider};
+use core::ops::Range;
+use raw::{
+    tables::{
+        gsub::{
+            ChainedSequenceContext, Gsub, SequenceContext, SingleSubst, SubstitutionLookupList,
+            SubstitutionSubtables,
+        },
+        layout::ScriptTags,
+        varc::CoverageTable,
+    },
+    types::Tag,
+    ReadError, TableProvider,
+};
+
+/// Determines the fidelity with which we apply shaping in the
+/// autohinter.
+///
+/// Shaping only affects glyph style classification and the glyphs that
+/// are chosen for metrics computations. We keep the `Nominal` mode around
+/// to enable validation of internal algorithms against a configuration that
+/// is known to match FreeType. The `BestEffort` mode should always be
+/// used for actual rendering.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub(crate) enum ShaperMode {
+    /// Characters are mapped to nominal glyph identifiers and layout tables
+    /// are not used for style coverage.
+    ///
+    /// This matches FreeType when HarfBuzz support is not enabled.
+    Nominal,
+    /// Simple substitutions are applied according to script rules and layout
+    /// tables are used to extend style coverage beyond the character map.
+    #[allow(unused)]
+    BestEffort,
+}
+
+#[derive(Copy, Clone, Default, Debug)]
+pub(crate) struct ShapedGlyph {
+    pub id: GlyphId,
+    /// This may be used for computing vertical alignment zones, particularly
+    /// for glyphs like super/subscripts which might have adjustments in GPOS.
+    ///
+    /// Note that we don't do the same in the horizontal direction which
+    /// means that we don't care about the x-offset.
+    pub y_offset: i32,
+}
+
+/// Arbitrarily chosen to cover our max input size plus some extra to account
+/// for expansion from multiple substitution tables.
+const SHAPED_CLUSTER_INLINE_SIZE: usize = 16;
+
+/// Container for storing the result of shaping a cluster.
+///
+/// Some of our input "characters" for metrics computations are actually
+/// multi-character [grapheme clusters](https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries)
+/// that may expand to multiple glyphs.
+pub(crate) type ShapedCluster = SmallVec<ShapedGlyph, SHAPED_CLUSTER_INLINE_SIZE>;
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub(crate) enum ShaperCoverageKind {
+    /// Shaper coverage that traverses a specific script.
+    Script,
+    /// Shaper coverage that also includes the `Dflt` script.
+    ///
+    /// This is used as a catch all after all styles are processed.
+    Default,
+}
+
+/// Maps characters to glyphs and handles extended style coverage beyond
+/// glyphs that are available in the character map.
+///
+/// Roughly covers the functionality in <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afshaper.c>.
+pub(crate) struct Shaper<'a> {
+    font: FontRef<'a>,
+    #[allow(unused)]
+    mode: ShaperMode,
+    charmap: Charmap<'a>,
+    gsub: Option<Gsub<'a>>,
+}
+
+impl<'a> Shaper<'a> {
+    pub fn new(font: &FontRef<'a>, mode: ShaperMode) -> Self {
+        let charmap = font.charmap();
+        let gsub = (mode != ShaperMode::Nominal)
+            .then(|| font.gsub().ok())
+            .flatten();
+        Self {
+            font: font.clone(),
+            mode,
+            charmap,
+            gsub,
+        }
+    }
+
+    pub fn font(&self) -> &FontRef<'a> {
+        &self.font
+    }
+
+    pub fn charmap(&self) -> &Charmap<'a> {
+        &self.charmap
+    }
+
+    /// Shapes the given input text with the current mode and stores the
+    /// resulting glyphs in the output cluster.
+    pub fn shape_cluster(&self, input: &str, output: &mut ShapedCluster) {
+        output.clear();
+        for (i, ch) in input.chars().enumerate() {
+            if i > 0 {
+                // In nominal mode, we reject input clusters with multiple
+                // characters
+                // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afshaper.c#L639>
+                output.clear();
+                return;
+            }
+            output.push(ShapedGlyph {
+                id: self.charmap.map(ch).unwrap_or_default(),
+                y_offset: 0,
+            });
+        }
+    }
+
+    /// Uses layout tables to compute coverage for the given style.
+    ///
+    /// Returns `true` if any glyph styles were updated for this style.
+    ///
+    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afshaper.c#L99>
+    pub(crate) fn compute_coverage(
+        &self,
+        style: &StyleClass,
+        coverage_kind: ShaperCoverageKind,
+        glyph_styles: &mut [GlyphStyle],
+    ) -> bool {
+        let Some(gsub) = self.gsub.as_ref() else {
+            return false;
+        };
+        let (Ok(script_list), Ok(feature_list), Ok(lookup_list)) =
+            (gsub.script_list(), gsub.feature_list(), gsub.lookup_list())
+        else {
+            return false;
+        };
+        let mut script_tags: [Option<Tag>; 3] = [None; 3];
+        for (a, b) in script_tags
+            .iter_mut()
+            .zip(ScriptTags::from_unicode(style.script.tag).iter())
+        {
+            *a = Some(*b);
+        }
+        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afshaper.c#L153>
+        const DEFAULT_SCRIPT: Tag = Tag::new(b"Dflt");
+        if coverage_kind == ShaperCoverageKind::Default {
+            if script_tags[0].is_none() {
+                script_tags[0] = Some(DEFAULT_SCRIPT);
+            } else if script_tags[1].is_none() {
+                script_tags[1] = Some(DEFAULT_SCRIPT);
+            } else if script_tags[1] != Some(DEFAULT_SCRIPT) {
+                script_tags[2] = Some(DEFAULT_SCRIPT);
+            }
+        } else {
+            // Script classes contain some non-standard tags used for special
+            // purposes. We ignore these
+            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afshaper.c#L167>
+            const NON_STANDARD_TAGS: &[Option<Tag>] = &[
+                // Khmer symbols
+                Some(Tag::new(b"Khms")),
+                // Latin subscript fallbacks
+                Some(Tag::new(b"Latb")),
+                // Latin superscript fallbacks
+                Some(Tag::new(b"Latp")),
+            ];
+            if NON_STANDARD_TAGS.contains(&script_tags[0]) {
+                return false;
+            }
+        }
+        // Check each requested script that is available in GSUB
+        let mut gsub_handler = GsubHandler::new(&self.charmap, &lookup_list, style, glyph_styles);
+        for script in script_tags.iter().filter_map(|tag| {
+            tag.and_then(|tag| script_list.index_for_tag(tag))
+                .and_then(|ix| script_list.script_records().get(ix as usize))
+                .and_then(|rec| rec.script(script_list.offset_data()).ok())
+        }) {
+            // And all language systems for each script
+            for langsys in script
+                .lang_sys_records()
+                .iter()
+                .filter_map(|rec| rec.lang_sys(script.offset_data()).ok())
+                .chain(script.default_lang_sys().transpose().ok().flatten())
+            {
+                for feature_ix in langsys.feature_indices() {
+                    let Some(feature) = feature_list
+                        .feature_records()
+                        .get(feature_ix.get() as usize)
+                        .and_then(|rec| {
+                            // If our style has a feature tag, we only look at that specific
+                            // feature; otherwise, handle all of them
+                            if style.feature == Some(rec.feature_tag()) || style.feature.is_none() {
+                                rec.feature(feature_list.offset_data()).ok()
+                            } else {
+                                None
+                            }
+                        })
+                    else {
+                        continue;
+                    };
+                    // And now process associated lookups
+                    for index in feature.lookup_list_indices().iter() {
+                        gsub_handler.process_lookup(index.get(), 0);
+                    }
+                }
+            }
+        }
+        if let Some(range) = gsub_handler.finish() {
+            // If we get a range then we captured at least some glyphs so
+            // let's try to assign our current style
+            let mut result = false;
+            for glyph_style in &mut glyph_styles[range] {
+                // We only want to return true here if we actually assign the
+                // style to avoid computing unnecessary metrics
+                result |= glyph_style.maybe_assign_gsub_output_style(style);
+            }
+            result
+        } else {
+            false
+        }
+    }
+}
+
+/// Captures glyphs from the GSUB table that aren't present in cmap.
+///
+/// FreeType does this in a few phases:
+/// 1. Collect all lookups for a given set of scripts and features.
+///    <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afshaper.c#L174>
+/// 2. For each lookup, collect all _output_ glyphs.
+///    <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afshaper.c#L201>
+/// 3. If the style represents a specific feature, make sure at least one of
+///    the characters in the associated blue string would be substituted by
+///    those lookups. If none would be substituted, then we don't assign the
+///    style to any glyphs because we don't have any modified alignment
+///    zones.
+///    <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afshaper.c#L264>
+///
+/// We roll these into one pass over the lookups below so that we don't have
+/// to allocate a lookup set or iterate them twice. Note that since
+/// substitutions are checked for individual characters, we ignore ligatures
+/// and contextual lookups (and alternates since they aren't applicable).
+struct GsubHandler<'a> {
+    charmap: &'a Charmap<'a>,
+    lookup_list: &'a SubstitutionLookupList<'a>,
+    style: &'a StyleClass,
+    glyph_styles: &'a mut [GlyphStyle],
+    // Set to true when we need to check if any substitutions are available
+    // for our blue strings. This is the case when style.feature != None
+    need_blue_substs: bool,
+    // Keep track of our range of touched gids in the style list
+    min_gid: usize,
+    max_gid: usize,
+}
+
+impl<'a> GsubHandler<'a> {
+    fn new(
+        charmap: &'a Charmap<'a>,
+        lookup_list: &'a SubstitutionLookupList,
+        style: &'a StyleClass,
+        glyph_styles: &'a mut [GlyphStyle],
+    ) -> Self {
+        let min_gid = glyph_styles.len();
+        // If we have a feature, then we need to check the blue string to see
+        // if any substitutions are available. If not, we don't enable this
+        // style because it won't have any affect on alignment zones
+        let need_blue_substs = style.feature.is_some();
+        Self {
+            charmap,
+            lookup_list,
+            style,
+            glyph_styles,
+            need_blue_substs,
+            min_gid,
+            max_gid: 0,
+        }
+    }
+
+    fn process_lookup(&mut self, lookup_index: u16, nesting_depth: u32) {
+        // To prevent infinite recursion in contextual lookups. Matches HB
+        // <https://github.com/harfbuzz/harfbuzz/blob/c7ef6a2ed58ae8ec108ee0962bef46f42c73a60c/src/hb-limits.hh#L53>
+        const MAX_NESTING_DEPTH: u32 = 64;
+        if nesting_depth > MAX_NESTING_DEPTH {
+            return;
+        }
+        let Ok(subtables) = self
+            .lookup_list
+            .lookups()
+            .get(lookup_index as usize)
+            .and_then(|lookup| lookup.subtables())
+        else {
+            return;
+        };
+        match subtables {
+            SubstitutionSubtables::Single(tables) => {
+                for table in tables.iter().filter_map(|table| table.ok()) {
+                    match table {
+                        SingleSubst::Format1(table) => {
+                            let Ok(coverage) = table.coverage() else {
+                                continue;
+                            };
+                            let delta = table.delta_glyph_id() as i32;
+                            for gid in coverage.iter() {
+                                self.capture_glyph((gid.to_u32() as i32 + delta) as u16 as u32);
+                            }
+                            // Check input coverage for blue strings if
+                            // required and if we're not under a contextual
+                            // lookup
+                            if self.need_blue_substs && nesting_depth == 0 {
+                                self.check_blue_coverage(Ok(coverage));
+                            }
+                        }
+                        SingleSubst::Format2(table) => {
+                            for gid in table.substitute_glyph_ids() {
+                                self.capture_glyph(gid.get().to_u32());
+                            }
+                            // See above
+                            if self.need_blue_substs && nesting_depth == 0 {
+                                self.check_blue_coverage(table.coverage());
+                            }
+                        }
+                    }
+                }
+            }
+            SubstitutionSubtables::Multiple(tables) => {
+                for table in tables.iter().filter_map(|table| table.ok()) {
+                    for seq in table.sequences().iter().filter_map(|seq| seq.ok()) {
+                        for gid in seq.substitute_glyph_ids() {
+                            self.capture_glyph(gid.get().to_u32());
+                        }
+                    }
+                    // See above
+                    if self.need_blue_substs && nesting_depth == 0 {
+                        self.check_blue_coverage(table.coverage());
+                    }
+                }
+            }
+            SubstitutionSubtables::Ligature(tables) => {
+                for table in tables.iter().filter_map(|table| table.ok()) {
+                    for set in table.ligature_sets().iter().filter_map(|set| set.ok()) {
+                        for lig in set.ligatures().iter().filter_map(|lig| lig.ok()) {
+                            self.capture_glyph(lig.ligature_glyph().to_u32());
+                        }
+                    }
+                }
+            }
+            SubstitutionSubtables::Alternate(tables) => {
+                for table in tables.iter().filter_map(|table| table.ok()) {
+                    for set in table.alternate_sets().iter().filter_map(|set| set.ok()) {
+                        for gid in set.alternate_glyph_ids() {
+                            self.capture_glyph(gid.get().to_u32());
+                        }
+                    }
+                }
+            }
+            SubstitutionSubtables::Contextual(tables) => {
+                for table in tables.iter().filter_map(|table| table.ok()) {
+                    match table {
+                        SequenceContext::Format1(table) => {
+                            for set in table
+                                .seq_rule_sets()
+                                .iter()
+                                .filter_map(|set| set.transpose().ok().flatten())
+                            {
+                                for rule in set.seq_rules().iter().filter_map(|rule| rule.ok()) {
+                                    for rec in rule.seq_lookup_records() {
+                                        self.process_lookup(
+                                            rec.lookup_list_index(),
+                                            nesting_depth + 1,
+                                        );
+                                    }
+                                }
+                            }
+                        }
+                        SequenceContext::Format2(table) => {
+                            for set in table
+                                .class_seq_rule_sets()
+                                .iter()
+                                .filter_map(|set| set.transpose().ok().flatten())
+                            {
+                                for rule in
+                                    set.class_seq_rules().iter().filter_map(|rule| rule.ok())
+                                {
+                                    for rec in rule.seq_lookup_records() {
+                                        self.process_lookup(
+                                            rec.lookup_list_index(),
+                                            nesting_depth + 1,
+                                        );
+                                    }
+                                }
+                            }
+                        }
+                        SequenceContext::Format3(table) => {
+                            for rec in table.seq_lookup_records() {
+                                self.process_lookup(rec.lookup_list_index(), nesting_depth + 1);
+                            }
+                        }
+                    }
+                }
+            }
+            SubstitutionSubtables::ChainContextual(tables) => {
+                for table in tables.iter().filter_map(|table| table.ok()) {
+                    match table {
+                        ChainedSequenceContext::Format1(table) => {
+                            for set in table
+                                .chained_seq_rule_sets()
+                                .iter()
+                                .filter_map(|set| set.transpose().ok().flatten())
+                            {
+                                for rule in
+                                    set.chained_seq_rules().iter().filter_map(|rule| rule.ok())
+                                {
+                                    for rec in rule.seq_lookup_records() {
+                                        self.process_lookup(
+                                            rec.lookup_list_index(),
+                                            nesting_depth + 1,
+                                        );
+                                    }
+                                }
+                            }
+                        }
+                        ChainedSequenceContext::Format2(table) => {
+                            for set in table
+                                .chained_class_seq_rule_sets()
+                                .iter()
+                                .filter_map(|set| set.transpose().ok().flatten())
+                            {
+                                for rule in set
+                                    .chained_class_seq_rules()
+                                    .iter()
+                                    .filter_map(|rule| rule.ok())
+                                {
+                                    for rec in rule.seq_lookup_records() {
+                                        self.process_lookup(
+                                            rec.lookup_list_index(),
+                                            nesting_depth + 1,
+                                        );
+                                    }
+                                }
+                            }
+                        }
+                        ChainedSequenceContext::Format3(table) => {
+                            for rec in table.seq_lookup_records() {
+                                self.process_lookup(rec.lookup_list_index(), nesting_depth + 1);
+                            }
+                        }
+                    }
+                }
+            }
+            SubstitutionSubtables::Reverse(tables) => {
+                for table in tables.iter().filter_map(|table| table.ok()) {
+                    for gid in table.substitute_glyph_ids() {
+                        self.capture_glyph(gid.get().to_u32());
+                    }
+                }
+            }
+        }
+    }
+
+    /// Finishes processing for this set of GSUB lookups and
+    /// returns the range of touched glyphs.
+    fn finish(self) -> Option<Range<usize>> {
+        if self.min_gid > self.max_gid {
+            // We didn't touch any glyphs
+            return None;
+        }
+        let range = self.min_gid..self.max_gid + 1;
+        if self.need_blue_substs {
+            // We didn't find any substitutions for our blue strings so
+            // we ignore the style. Clear the GSUB marker for any touched
+            // glyphs
+            for glyph in &mut self.glyph_styles[range] {
+                glyph.clear_from_gsub();
+            }
+            None
+        } else {
+            Some(range)
+        }
+    }
+
+    /// Checks the given coverage table for any characters in the blue
+    /// strings associated with our current style.
+    fn check_blue_coverage(&mut self, coverage: Result<CoverageTable<'a>, ReadError>) {
+        let Ok(coverage) = coverage else {
+            return;
+        };
+        for (blue_str, _) in self.style.script.blues {
+            if blue_str
+                .chars()
+                .filter_map(|ch| self.charmap.map(ch))
+                .filter_map(|gid| coverage.get(gid))
+                .next()
+                .is_some()
+            {
+                // Condition satisfied, so don't check any further subtables
+                self.need_blue_substs = false;
+                return;
+            }
+        }
+    }
+
+    fn capture_glyph(&mut self, gid: u32) {
+        let gid = gid as usize;
+        if let Some(style) = self.glyph_styles.get_mut(gid) {
+            style.set_from_gsub_output();
+            self.min_gid = gid.min(self.min_gid);
+            self.max_gid = gid.max(self.max_gid);
+        }
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/style.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/style.rs
similarity index 74%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/style.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/style.rs
index 30515fdc..3d707048 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/style.rs
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/style.rs
@@ -1,8 +1,8 @@
 //! Styles, scripts and glyph style mapping.
 
-use crate::{charmap::Charmap, GlyphId};
+use super::shape::ShaperCoverageKind;
 use alloc::vec::Vec;
-use raw::types::Tag;
+use raw::types::{GlyphId, Tag};
 
 /// Defines the script and style associated with a single glyph.
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
@@ -16,11 +16,15 @@
     // information differently.
     const STYLE_INDEX_MASK: u16 = 0xFF;
     const UNASSIGNED: u16 = Self::STYLE_INDEX_MASK;
+    // A non-base character, perhaps more commonly referred to as a "mark"
     const NON_BASE: u16 = 0x100;
     const DIGIT: u16 = 0x200;
+    // Used as intermediate state to mark when a glyph appears as GSUB output
+    // for a given script
+    const FROM_GSUB_OUTPUT: u16 = 0x8000;
 
     pub const fn is_unassigned(self) -> bool {
-        self.0 == Self::UNASSIGNED
+        self.0 & Self::STYLE_INDEX_MASK == Self::UNASSIGNED
     }
 
     pub const fn is_non_base(self) -> bool {
@@ -58,6 +62,31 @@
             self.0 = (self.0 & !Self::STYLE_INDEX_MASK) | other.0;
         }
     }
+
+    pub(super) fn set_from_gsub_output(&mut self) {
+        self.0 |= Self::FROM_GSUB_OUTPUT
+    }
+
+    pub(super) fn clear_from_gsub(&mut self) {
+        self.0 &= !Self::FROM_GSUB_OUTPUT;
+    }
+
+    /// Assign a style if we've been marked as GSUB output _and_ the
+    /// we don't currently have an assigned style.
+    ///
+    /// This also clears the GSUB output bit.
+    ///
+    /// Returns `true` if this style was applied.
+    pub(super) fn maybe_assign_gsub_output_style(&mut self, style: &StyleClass) -> bool {
+        let style_ix = style.index as u16;
+        if self.0 & Self::FROM_GSUB_OUTPUT != 0 && self.is_unassigned() {
+            self.clear_from_gsub();
+            self.0 = (self.0 & !Self::STYLE_INDEX_MASK) | style_ix;
+            true
+        } else {
+            false
+        }
+    }
 }
 
 impl Default for GlyphStyle {
@@ -66,6 +95,9 @@
     }
 }
 
+/// Sentinel for unused styles in [`GlyphStyleMap::metrics_map`].
+const UNMAPPED_STYLE: u8 = 0xFF;
+
 /// Maps glyph identifiers to glyph styles.
 ///
 /// Also keeps track of the styles that are actually used so we can allocate
@@ -88,19 +120,26 @@
     /// map.
     ///
     /// Roughly based on <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afglobal.c#L126>
-    pub fn new(glyph_count: u32, charmap: &Charmap) -> Self {
+    pub fn new(glyph_count: u32, shaper: &Shaper) -> Self {
         let mut map = Self {
             styles: vec![GlyphStyle::default(); glyph_count as usize],
-            metrics_map: [0xFF; MAX_STYLES],
+            metrics_map: [UNMAPPED_STYLE; MAX_STYLES],
             metrics_count: 0,
         };
-        // TODO: Step 1: compute styles for glyphs covered by OpenType features
-        // --
+        // Step 1: compute styles for glyphs covered by OpenType features
+        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afglobal.c#L233>
+        for style in super::style::STYLE_CLASSES {
+            if style.feature.is_some()
+                && shaper.compute_coverage(style, ShaperCoverageKind::Script, &mut map.styles)
+            {
+                map.use_style(style.index);
+            }
+        }
         // Step 2: compute styles for glyphs contained in the cmap
         // cmap entries are sorted so we keep track of the most recent range to
         // avoid a binary search per character
         let mut last_range: Option<(usize, StyleRange)> = None;
-        for (ch, gid) in charmap.mappings() {
+        for (ch, gid) in shaper.charmap().mappings() {
             let Some(style) = map.styles.get_mut(gid.to_u32() as usize) else {
                 continue;
             };
@@ -122,19 +161,27 @@
             if range.contains(ch) {
                 style.maybe_assign(range.style);
                 if let Some(style_ix) = range.style.style_index() {
-                    let style_ix = style_ix as usize;
-                    if map.metrics_map[style_ix] == 0xFF {
-                        // This the first time we've seen this style so record
-                        // it in the metrics map
-                        map.metrics_map[style_ix] = map.metrics_count;
-                        map.metrics_count += 1;
-                    }
+                    map.use_style(style_ix as usize);
                 }
                 last_range = Some((ix, range));
             }
         }
-        // TODO: Step 3: compute script based coverage
-        // --
+        // Step 3a: compute script based coverage
+        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afglobal.c#L239>
+        for style in super::style::STYLE_CLASSES {
+            if style.feature.is_none()
+                && shaper.compute_coverage(style, ShaperCoverageKind::Script, &mut map.styles)
+            {
+                map.use_style(style.index);
+            }
+        }
+        // Step 3b: compute coverage for "default" script which is always set
+        // to Latin in FreeType
+        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afglobal.c#L248>
+        let default_style = &STYLE_CLASSES[StyleClass::LATN];
+        if shaper.compute_coverage(default_style, ShaperCoverageKind::Default, &mut map.styles) {
+            map.use_style(default_style.index);
+        }
         // Step 4: Assign a default to all remaining glyphs
         // For some reason, FreeType uses Hani as a default fallback style so
         // let's do the same.
@@ -148,11 +195,7 @@
             }
         }
         if need_hani {
-            let mapped_ix = &mut map.metrics_map[StyleClass::HANI];
-            if *mapped_ix == 0xFF {
-                map.metrics_map[StyleClass::HANI] = map.metrics_count;
-                map.metrics_count += 1;
-            }
+            map.use_style(StyleClass::HANI);
         }
         map
     }
@@ -165,7 +208,7 @@
     pub fn metrics_index(&self, style: GlyphStyle) -> Option<usize> {
         let ix = style.style_index()? as usize;
         let metrics_ix = *self.metrics_map.get(ix)? as usize;
-        if metrics_ix != 0xFF {
+        if metrics_ix != UNMAPPED_STYLE as usize {
             Some(metrics_ix)
         } else {
             None
@@ -181,9 +224,9 @@
     /// this map.
     pub fn metrics_styles(&self) -> impl Iterator<Item = &'static StyleClass> + '_ {
         // Need to build a reverse map so that these are properly ordered
-        let mut reverse_map = [0xFF; MAX_STYLES];
+        let mut reverse_map = [UNMAPPED_STYLE; MAX_STYLES];
         for (ix, &entry) in self.metrics_map.iter().enumerate() {
-            if entry != 0xFF {
+            if entry != UNMAPPED_STYLE {
                 reverse_map[entry as usize] = ix as u8;
             }
         }
@@ -191,20 +234,30 @@
             .into_iter()
             .enumerate()
             .filter_map(move |(mapped, style_ix)| {
-                if mapped == 0xFF {
+                if mapped == UNMAPPED_STYLE as usize {
                     None
                 } else {
                     STYLE_CLASSES.get(style_ix as usize)
                 }
             })
     }
+
+    fn use_style(&mut self, style_ix: usize) {
+        let mapped = &mut self.metrics_map[style_ix];
+        if *mapped == UNMAPPED_STYLE {
+            // This the first time we've seen this style so record
+            // it in the metrics map
+            *mapped = self.metrics_count;
+            self.metrics_count += 1;
+        }
+    }
 }
 
 impl Default for GlyphStyleMap {
     fn default() -> Self {
         Self {
             styles: Default::default(),
-            metrics_map: [0xFF; MAX_STYLES],
+            metrics_map: [UNMAPPED_STYLE; MAX_STYLES],
             metrics_count: 0,
         }
     }
@@ -320,20 +373,20 @@
 
 use blue_flags::*;
 
+use super::shape::Shaper;
+
 include!("../../../generated/generated_autohint_styles.rs");
 
 #[cfg(test)]
 mod tests {
-    use super::*;
-    use crate::{raw::TableProvider, FontRef, MetadataProvider};
+    use super::{super::shape::ShaperMode, *};
+    use crate::{raw::TableProvider, FontRef};
 
     #[test]
     fn glyph_styles() {
-        let font = FontRef::new(font_test_data::AUTOHINT_CMAP).unwrap();
-        let num_glyphs = font.maxp().unwrap().num_glyphs() as u32;
-        let style_map = GlyphStyleMap::new(num_glyphs, &font.charmap());
         // generated by printf debugging in FreeType
-        // (gid, Option<(script_name, is_non_base)>)
+        // (gid, Option<(script_name, is_non_base_char)>)
+        // where "is_non_base_char" more common means "is_mark"
         let expected = &[
             (0, Some(("CJKV ideographs", false))),
             (1, Some(("Latin", true))),
@@ -434,13 +487,47 @@
             (96, Some(("Adlam", true))),
             (97, Some(("Adlam", false))),
         ];
+        check_styles(font_test_data::AUTOHINT_CMAP, ShaperMode::Nominal, expected);
+    }
+
+    #[test]
+    fn shaped_glyph_styles() {
+        // generated by printf debugging in FreeType
+        // (gid, Option<(script_name, is_non_base_char)>)
+        // where "is_non_base_char" more common means "is_mark"
+        let expected = &[
+            (0, Some(("CJKV ideographs", false))),
+            (1, Some(("Latin", false))),
+            (2, Some(("Latin", false))),
+            (3, Some(("Latin", false))),
+            (4, Some(("Latin", false))),
+            // Note: ligatures starting with 'f' are assigned the Cyrillic
+            // script which matches FreeType
+            (5, Some(("Cyrillic", false))),
+            (6, Some(("Cyrillic", false))),
+            (7, Some(("Cyrillic", false))),
+            // Capture the Latin c2sc feature
+            (8, Some(("Latin small capitals from capitals", false))),
+        ];
+        check_styles(
+            font_test_data::NOTOSERIF_AUTOHINT_SHAPING,
+            ShaperMode::BestEffort,
+            expected,
+        );
+    }
+
+    fn check_styles(font_data: &[u8], mode: ShaperMode, expected: &[(u32, Option<(&str, bool)>)]) {
+        let font = FontRef::new(font_data).unwrap();
+        let shaper = Shaper::new(&font, mode);
+        let num_glyphs = font.maxp().unwrap().num_glyphs() as u32;
+        let style_map = GlyphStyleMap::new(num_glyphs, &shaper);
         let results = style_map
             .styles
             .iter()
             .enumerate()
             .map(|(gid, style)| {
                 (
-                    gid as i32,
+                    gid as u32,
                     style
                         .style_class()
                         .map(|style_class| (style_class.name, style.is_non_base())),
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/cff/hint.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/cff/hint.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/cff/hint.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/cff/hint.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/cff/mod.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/cff/mod.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/cff/mod.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/cff/mod.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/common.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/common.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/common.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/common.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/error.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/error.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/error.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/error.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/deltas.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/deltas.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/deltas.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/deltas.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/call_stack.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/call_stack.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/call_stack.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/call_stack.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/cow_slice.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/cow_slice.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/cow_slice.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/cow_slice.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/cvt.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/cvt.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/cvt.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/cvt.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/definition.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/definition.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/definition.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/definition.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/arith.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/arith.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/arith.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/arith.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/control_flow.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/control_flow.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/control_flow.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/control_flow.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/cvt.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/cvt.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/cvt.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/cvt.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/data.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/data.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/data.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/data.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/definition.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/definition.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/definition.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/definition.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/delta.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/delta.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/delta.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/delta.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/dispatch.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/dispatch.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/dispatch.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/dispatch.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/graphics.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/graphics.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/graphics.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/graphics.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/logical.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/logical.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/logical.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/logical.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/misc.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/misc.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/misc.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/misc.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/mod.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/mod.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/mod.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/mod.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/outline.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/outline.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/outline.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/outline.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/round.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/round.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/round.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/round.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/stack.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/stack.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/stack.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/stack.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/storage.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/storage.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/storage.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/storage.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/error.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/error.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/error.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/error.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/graphics.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/graphics.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/graphics.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/graphics.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/instance.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/instance.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/instance.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/instance.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/math.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/math.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/math.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/math.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/mod.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/mod.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/mod.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/mod.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/program.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/program.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/program.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/program.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/projection.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/projection.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/projection.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/projection.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/round.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/round.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/round.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/round.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/storage.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/storage.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/storage.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/storage.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/value_stack.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/value_stack.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/value_stack.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/value_stack.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/zone.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/zone.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/zone.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/zone.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/memory.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/memory.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/memory.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/memory.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/mod.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/mod.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/mod.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/mod.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/outline.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/outline.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/outline.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/outline.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/hint.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/hint.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/hint.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/hint.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/mod.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/mod.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/mod.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/mod.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/path.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/path.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/path.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/path.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/pen.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/pen.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/pen.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/pen.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/testing.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/testing.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/testing.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/testing.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/unscaled.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/unscaled.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/unscaled.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/unscaled.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/patchmap.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/patchmap.rs
similarity index 89%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/patchmap.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/patchmap.rs
index 02d67ed..9830a7a 100644
--- a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/patchmap.rs
+++ b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/patchmap.rs
@@ -24,6 +24,8 @@
 
 use crate::charmap::Charmap;
 
+// TODO(garretrieger): implement support for building and compiling mapping tables.
+
 /// Find the set of patches which intersect the specified subset definition.
 pub fn intersecting_patches(
     font: &FontRef,
@@ -328,7 +330,6 @@
     if let Some(design_space_segments) = entry_data.design_space_segments() {
         for dss in design_space_segments {
             if dss.start() > dss.end() {
-                // TODO(garretrieger): ensure spec has language enforcing this.
                 return Err(ReadError::MalformedData(
                     "Design space segment start > end.",
                 ));
@@ -426,6 +427,7 @@
             .entry_id_delta()
             .map(|v| v.into_inner() as i64)
             .unwrap_or(0);
+
     if new_index.is_negative() {
         return Err(ReadError::MalformedData("Negative entry id encountered."));
     }
@@ -463,9 +465,6 @@
         return Err(ReadError::MalformedData("Codepoints data is too short."));
     };
 
-    // TODO(garretrieger): the spec doesn't currently enforce a maximum set size, but here we are
-    // disallowing sets with more members then the unicode max value. The spec should be updated to
-    // provide a reasonable maximum set size.
     let (set, remaining_data) =
         IntSet::<u32>::from_sparse_bit_set_bounded(codepoint_data.as_bytes(), bias, 0x10FFFF)
             .map_err(|_| {
@@ -654,6 +653,7 @@
         codepoints_only_format2, copy_indices_format2, custom_ids_format2, feature_map_format1,
         features_and_design_space_format2, simple_format1, string_ids_format2, u16_entries_format1,
     };
+    use raw::types::Int24;
     use read_fonts::tables::ift::{IFTX_TAG, IFT_TAG};
     use read_fonts::FontRef;
     use write_fonts::FontBuilder;
@@ -1096,8 +1096,9 @@
         test_intersection(&font, [0x02], [], [1]);
         test_intersection(&font, [0x15], [], [3]);
         test_intersection(&font, [0x07], [], [1, 3]);
+        test_intersection(&font, [80_007], [], [4]);
 
-        test_intersection_with_all(&font, [], [1, 3]);
+        test_intersection_with_all(&font, [], [1, 3, 4]);
     }
 
     #[test]
@@ -1177,7 +1178,7 @@
         let font = FontRef::new(&font_bytes).unwrap();
 
         test_intersection(&font, [], [], []);
-        test_intersection(&font, [0x05], [], [1, 5, 9]);
+        test_intersection(&font, [0x05], [], [5, 9]);
         test_intersection(&font, [0x65], [], [9]);
 
         test_design_space_intersection(
@@ -1193,7 +1194,7 @@
             [0x05],
             [Tag::new(b"rlig")],
             [(Tag::new(b"wght"), vec![500.0..=500.0])],
-            [1, 3, 5, 6, 7, 8, 9],
+            [3, 5, 6, 7, 8, 9],
         );
     }
 
@@ -1239,7 +1240,7 @@
     }
 
     #[test]
-    fn format_2_id_strings() {
+    fn format_2_patch_map_id_strings() {
         let font_bytes = create_ift_font(
             FontRef::new(test_data::ift::IFT_BASE).unwrap(),
             Some(&string_ids_format2()),
@@ -1265,7 +1266,7 @@
     }
 
     #[test]
-    fn format_2_id_strings_too_short() {
+    fn format_2_patch_map_id_strings_too_short() {
         let mut data = string_ids_format2();
         data.write_at("entry[4] id length", 4u16);
 
@@ -1283,13 +1284,134 @@
         .is_err());
     }
 
-    // TODO(garretrieger): test decoding of other entry features for format 2
-    // - invalid cases: add once spec has been updated with validation requirements.
-    //   - start < end for ds segments
-    //   - sparse bit set to short
-    //   - negative entry indices
-    //   - too large entry indices
-    // - 24 bit bias.
-    // - no codepoints
-    // - ignored entries can still be copied
+    #[test]
+    fn format_2_patch_map_invalid_design_space() {
+        let mut data = features_and_design_space_format2();
+        data.write_at("wdth start", 0x20000u32);
+
+        let font_bytes = create_ift_font(
+            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
+            Some(&data),
+            None,
+        );
+        let font = FontRef::new(&font_bytes).unwrap();
+
+        assert!(intersecting_patches(
+            &font,
+            &SubsetDefinition::new(IntSet::all(), BTreeSet::new(), HashMap::new()),
+        )
+        .is_err());
+    }
+
+    #[test]
+    fn format_2_patch_map_invalid_sparse_bit_set() {
+        let data = codepoints_only_format2();
+        let font_bytes = create_ift_font(
+            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
+            Some(&data[..(data.len() - 1)]),
+            None,
+        );
+        let font = FontRef::new(&font_bytes).unwrap();
+
+        assert!(intersecting_patches(
+            &font,
+            &SubsetDefinition::new(IntSet::all(), BTreeSet::new(), HashMap::new()),
+        )
+        .is_err());
+    }
+
+    #[test]
+    fn format_2_patch_map_negative_entry_id() {
+        let mut data = custom_ids_format2();
+        data.write_at("id delta", Int24::new(-2));
+
+        let font_bytes = create_ift_font(
+            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
+            Some(&data),
+            None,
+        );
+        let font = FontRef::new(&font_bytes).unwrap();
+
+        assert!(intersecting_patches(
+            &font,
+            &SubsetDefinition::new(IntSet::all(), BTreeSet::new(), HashMap::new()),
+        )
+        .is_err());
+    }
+
+    #[test]
+    fn format_2_patch_map_negative_entry_id_on_ignored() {
+        let mut data = custom_ids_format2();
+        data.write_at("id delta - ignored entry", Int24::new(-20));
+
+        let font_bytes = create_ift_font(
+            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
+            Some(&data),
+            None,
+        );
+        let font = FontRef::new(&font_bytes).unwrap();
+
+        assert!(intersecting_patches(
+            &font,
+            &SubsetDefinition::new(IntSet::all(), BTreeSet::new(), HashMap::new()),
+        )
+        .is_err());
+    }
+
+    #[test]
+    fn format_2_patch_map_entry_id_overflow() {
+        let count = 511;
+        let mut data = custom_ids_format2();
+        data.write_at("entry_count", Uint24::new(count + 5));
+
+        for _ in 0..count {
+            data = data
+                .push(0b01000100u8) // format = ID_DELTA | IGNORED
+                .push(Int24::new(0x7FFFFF)); // delta = max(i24)
+        }
+
+        // at this point the second last entry id is:
+        // 15 +                   # last entry id from the first 4 entries
+        // count * (0x7FFFFF + 1) # sum of added deltas
+        //
+        // So the max delta without overflow on the last entry is:
+        //
+        // u32::MAX - second last entry id - 1
+        //
+        // The -1 is needed because the last entry implicitly includes a + 1
+        let max_delta_without_overflow = (u32::MAX - ((15 + count * (0x7FFFFF + 1)) + 1)) as i32;
+        data = data
+            .push(0b01000100u8) // format = ID_DELTA | IGNORED
+            .push_with_tag(Int24::new(max_delta_without_overflow), "last delta"); // delta
+
+        // Check one less than max doesn't overflow
+        let font_bytes = create_ift_font(
+            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
+            Some(&data),
+            None,
+        );
+        let font = FontRef::new(&font_bytes).unwrap();
+
+        assert!(intersecting_patches(
+            &font,
+            &SubsetDefinition::new(IntSet::all(), BTreeSet::new(), HashMap::new()),
+        )
+        .is_ok());
+
+        // Check one more does overflow
+        data.write_at("last delta", Int24::new(max_delta_without_overflow + 1));
+
+        let font_bytes = create_ift_font(
+            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
+            Some(&data),
+            None,
+        );
+        let font = FontRef::new(&font_bytes).unwrap();
+
+        assert!(intersecting_patches(
+            &font,
+            &SubsetDefinition::new(IntSet::all(), BTreeSet::new(), HashMap::new()),
+        )
+        .is_err());
+    }
 }
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/provider.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/provider.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/provider.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/provider.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/setting.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/setting.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/setting.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/setting.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/string.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/string.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/string.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/string.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/variation.rs b/third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/variation.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/variation.rs
rename to third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/variation.rs
diff --git a/third_party/rust/skrifa/v0_22/BUILD.gn b/third_party/rust/skrifa/v0_22/BUILD.gn
index f2c098c9..bc39dc1 100644
--- a/third_party/rust/skrifa/v0_22/BUILD.gn
+++ b/third_party/rust/skrifa/v0_22/BUILD.gn
@@ -13,91 +13,92 @@
   epoch = "0.22"
   crate_type = "rlib"
   crate_root =
-      "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/lib.rs"
+      "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/lib.rs"
   sources = [
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/attribute.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/charmap.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/collections.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/instance.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/mod.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/transform.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/traversal.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/traversal_tests/mod.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/color/traversal_tests/test_glyph_defs.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/font.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/instance.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/lib.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/metrics.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/axis.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/cycling.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/hint.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/instance.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/blues.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/edges.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/hint.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/metrics.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/mod.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/segments.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/latin/widths.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/metrics.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/mod.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/outline.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/autohint/style.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/cff/hint.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/cff/mod.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/common.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/error.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/deltas.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/call_stack.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/cow_slice.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/cvt.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/definition.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/arith.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/control_flow.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/cvt.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/data.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/definition.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/delta.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/dispatch.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/graphics.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/logical.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/misc.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/mod.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/outline.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/round.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/stack.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/engine/storage.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/error.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/graphics.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/instance.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/math.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/mod.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/program.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/projection.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/round.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/storage.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/value_stack.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/hint/zone.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/memory.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/mod.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/glyf/outline.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/hint.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/mod.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/path.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/pen.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/testing.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/outline/unscaled.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/patchmap.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/provider.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/setting.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/string.rs",
-    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/variation.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/attribute.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/charmap.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/collections.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/instance.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/transform.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/traversal.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/traversal_tests/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/color/traversal_tests/test_glyph_defs.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/font.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/instance.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/lib.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/metrics.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/axis.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/cycling.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/hint.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/instance.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/blues.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/edges.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/hint.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/metrics.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/segments.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/latin/widths.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/metrics.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/outline.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/shape.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/autohint/style.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/cff/hint.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/cff/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/common.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/error.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/deltas.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/call_stack.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/cow_slice.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/cvt.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/definition.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/arith.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/control_flow.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/cvt.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/data.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/definition.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/delta.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/dispatch.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/graphics.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/logical.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/misc.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/outline.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/round.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/stack.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/engine/storage.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/error.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/graphics.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/instance.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/math.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/program.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/projection.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/round.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/storage.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/value_stack.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/hint/zone.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/memory.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/glyf/outline.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/hint.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/path.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/pen.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/testing.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/outline/unscaled.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/patchmap.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/provider.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/setting.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/string.rs",
+    "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/variation.rs",
   ]
-  inputs = [ "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/src/../generated/generated_autohint_styles.rs" ]
+  inputs = [ "//third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/src/../generated/generated_autohint_styles.rs" ]
 
   build_native_rust_unit_tests = false
   edition = "2021"
-  cargo_pkg_version = "0.22.0"
+  cargo_pkg_version = "0.22.1"
   cargo_pkg_name = "skrifa"
   cargo_pkg_description = "Metadata reader and glyph scaler for OpenType fonts."
   library_configs -= [ "//build/config/compiler:chromium_code" ]
@@ -111,6 +112,7 @@
     "//third_party/rust/read_fonts/v0_22:lib",
   ]
   features = [
+    "autohint_shaping",
     "std",
     "traversal",
   ]
diff --git a/third_party/rust/skrifa/v0_22/README.chromium b/third_party/rust/skrifa/v0_22/README.chromium
index e371619a..74c6b59 100644
--- a/third_party/rust/skrifa/v0_22/README.chromium
+++ b/third_party/rust/skrifa/v0_22/README.chromium
@@ -1,9 +1,9 @@
 Name: skrifa
 URL: https://crates.io/crates/skrifa
 Description: Metadata reader and glyph scaler for OpenType fonts.
-Version: 0.22.0
+Version: 0.22.1
 Security Critical: yes
 Shipped: yes
 License: Apache 2.0
-License File: //third_party/rust/chromium_crates_io/vendor/skrifa-0.22.0/LICENSE-APACHE
-Revision: 6b58785c80e2641a945863301f1f0f270902766c
+License File: //third_party/rust/chromium_crates_io/vendor/skrifa-0.22.1/LICENSE-APACHE
+Revision: f80507c47a9974863c735af5162d76dba4daf8bf
diff --git a/third_party/skia b/third_party/skia
index 5fb36dd..0411eaf 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 5fb36dd08a257623ee0738747286de09662e4591
+Subproject commit 0411eaf35f69e2a74415775eddab18aa00048ba8
diff --git a/third_party/webgpu-cts/src b/third_party/webgpu-cts/src
index 9d029d3..c42a89c 160000
--- a/third_party/webgpu-cts/src
+++ b/third_party/webgpu-cts/src
@@ -1 +1 @@
-Subproject commit 9d029d337ae1832b5a7b9bf049a87076c12f749d
+Subproject commit c42a89c461fb3c6c375f2d1e09275ed9f99d3ddf
diff --git a/third_party/webrtc b/third_party/webrtc
index 076eb6c..12f15e9 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit 076eb6cdf236cd6125ab126df2340ca3ee265425
+Subproject commit 12f15e99c274efe531a31ceffcc5edb6b1111cf3
diff --git a/tools/OWNERS b/tools/OWNERS
index ed9452c..e627b088 100644
--- a/tools/OWNERS
+++ b/tools/OWNERS
@@ -6,8 +6,8 @@
 # OWNERS of the existing tool.
 
 set noparent
-brucedawson@chromium.org
 dpranke@google.com
+jessemckenna@google.com
 thakis@chromium.org
 
 per-file add_header*.py=dcheng@chromium.org
diff --git a/tools/json_schema_compiler/test/web_idl/basics.idl b/tools/json_schema_compiler/test/web_idl/basics.idl
index c344bc43..8153fb7 100644
--- a/tools/json_schema_compiler/test/web_idl/basics.idl
+++ b/tools/json_schema_compiler/test/web_idl/basics.idl
@@ -18,6 +18,14 @@
   static double returnsDouble();
   static long returnsLong();
   static DOMString returnsDOMString();
+
+  static void takesNoArguments();
+  static void takesDOMString(DOMString stringArgument);
+  static void takesOptionalBoolean(optional boolean optionalBoolean);
+  static void takesMultipleArguments(
+      DOMString argument1, optional double argument2);
+  static void takesOptionalInnerArgument(
+      DOMString first, optional DOMString optionalInner, DOMString last);
 };
 
 partial interface Browser {
diff --git a/tools/json_schema_compiler/web_idl_schema.py b/tools/json_schema_compiler/web_idl_schema.py
index 33e254d5..5d190a2 100755
--- a/tools/json_schema_compiler/web_idl_schema.py
+++ b/tools/json_schema_compiler/web_idl_schema.py
@@ -110,16 +110,19 @@
         'Could not find Type node on IDLNode named: %s.' % (node.GetName()))
     self.node = type_node
     self.name = node.GetName()
+    self.optional = node.GetProperty('OPTIONAL')
 
   def process(self) -> dict:
     properties = OrderedDict()
-    # TODO(crbug.com/340297705): Add support for optional types.
     # TODO(crbug.com/340297705): Add support for extended attributes on types.
     # TODO(crbug.com/340297705): Add processing of comments to descriptions on
     #                            types.
     properties['name'] = self.name
-    if self.node.GetProperty('NULLABLE'):
+    # We consider both nullable properties on types or arguments marked as
+    # optional as being "optional" in the schema compiler's logic..
+    if self.node.GetProperty('NULLABLE') or self.optional:
       properties['optional'] = True
+
     # TODO(crbug.com/340297705): Add support for more types, including TypeRefs.
     basic_type = self.node.GetOneOf('PrimitiveType', 'StringType')
     if basic_type:
@@ -165,7 +168,15 @@
     properties = OrderedDict()
     properties['name'] = self.node.GetName()
 
+    parameters = []
+    arguments_node = self.node.GetOneOf('Arguments')
+    for argument in arguments_node.GetListOf('Argument'):
+      parameters.append(Type(argument).process())
+    properties['parameters'] = parameters
+
     # Return type processing.
+    # TODO(crbug.com/340297705): Add support for turning a Promise return into a
+    # returns_async property.
     return_type = Type(self.node).process()
     if return_type is not None:
       properties['returns'] = return_type
diff --git a/tools/json_schema_compiler/web_idl_schema_test.py b/tools/json_schema_compiler/web_idl_schema_test.py
index a6a03196..1419719 100755
--- a/tools/json_schema_compiler/web_idl_schema_test.py
+++ b/tools/json_schema_compiler/web_idl_schema_test.py
@@ -27,6 +27,11 @@
   return function.get('returns', None)
 
 
+def getFunctionParameters(schema, name):
+  function = getFunction(schema, name)
+  return function.get('parameters', None)
+
+
 class WebIdlSchemaTest(unittest.TestCase):
 
   def setUp(self):
@@ -71,6 +76,42 @@
         getReturns(schema, 'returnsDOMString'),
     )
 
+  # Tests function parameters are processed as expected.
+  def testFunctionParameters(self):
+    schema = self.idl_basics
+    # A function with no arguments has an empty array on the "parameters" key.
+    self.assertEqual([], getFunctionParameters(schema, 'takesNoArguments'))
+
+    self.assertEqual([{
+        'name': 'stringArgument',
+        'type': 'string'
+    }], getFunctionParameters(schema, 'takesDOMString'))
+    self.assertEqual([{
+        'name': 'optionalBoolean',
+        'optional': True,
+        'type': 'boolean'
+    }], getFunctionParameters(schema, 'takesOptionalBoolean'))
+    self.assertEqual([{
+        'name': 'argument1',
+        'type': 'string'
+    }, {
+        'name': 'argument2',
+        'optional': True,
+        'type': 'number'
+    }], getFunctionParameters(schema, 'takesMultipleArguments'))
+    self.assertEqual([{
+        'name': 'first',
+        'type': 'string'
+    }, {
+        'name': 'optionalInner',
+        'optional': True,
+        'type': 'string'
+    }, {
+        'name': 'last',
+        'type': 'string'
+    }], getFunctionParameters(schema, 'takesOptionalInnerArgument'))
+
+
   # Tests that Dictionaries defined on the top level of the IDL file are
   # processed into types on the resulting namespace.
   def testApiTypesOnNamespace(self):
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 33bd97b..cead947 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -11002,6 +11002,7 @@
   <int value="5110"
       label="V8GPUSupportedLimits_MaxInterStageShaderComponents_AttributeGetter"/>
   <int value="5111" label="MaxInterStageShaderComponentsRequiredLimit"/>
+  <int value="5112" label="ShowPickerSelect"/>
 </enum>
 
 <!-- LINT.ThenChange(//third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom:WebFeature) -->
@@ -19219,6 +19220,7 @@
   <int value="-454459100" label="ReadAnythingWithAlgorithm:enabled"/>
   <int value="-454362199" label="HelpAppV2:disabled"/>
   <int value="-454359275" label="kQuickAnswersMaterialNextUI:disabled"/>
+  <int value="-454246685" label="AndroidAppIntegrationWithFavicon:disabled"/>
   <int value="-452734575" label="ArcExtendInputAnrTimeout:enabled"/>
   <int value="-451898735" label="private-aggregation-developer-mode"/>
   <int value="-450976085"
@@ -19603,6 +19605,7 @@
       label="AutofillEnableMerchantDomainInUnmaskCardRequest:enabled"/>
   <int value="-296179618" label="CookiesWithoutSameSiteMustBeSecure:enabled"/>
   <int value="-295753272" label="CaptivePortalPopupWindow:disabled"/>
+  <int value="-295715134" label="AndroidAppIntegrationWithFavicon:enabled"/>
   <int value="-295237704" label="EnableRemovingAllThirdPartyCookies:enabled"/>
   <int value="-293546827" label="HideIncognitoMediaMetadata:enabled"/>
   <int value="-292546921" label="CrabbyAvif:disabled"/>
@@ -20511,6 +20514,7 @@
   <int value="86386196"
       label="SafeBrowsingExtensionTelemetryForEnterprise:enabled"/>
   <int value="86563978" label="CCTTextFragmentLookupApiEnabled:enabled"/>
+  <int value="86582659" label="enable-extension-ai-data-collection"/>
   <int value="86900696" label="SanitizerAPIv0:enabled"/>
   <int value="87306743" label="VariationsFakeCrashAfterStartup:disabled"/>
   <int value="88437020" label="FeaturePolicy:enabled"/>
@@ -34036,6 +34040,7 @@
   <int value="124" label="Aborting"/>
   <int value="125" label="EditContext"/>
   <int value="126" label="PaintOrder"/>
+  <int value="127" label="ShowPickerSelect"/>
 </enum>
 
 <!-- LINT.ThenChange(//third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom:WebDXFeature) -->
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index 3aaf4e48..af0d1a2 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -1261,7 +1261,7 @@
 <histogram name="ChromeOS.Debugd.Perf.GetBigFeedbackLogs.{SubTaskName}"
     units="ms" expires_after="2023-07-01">
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the total time it takes for each sub task of
     LogTool::GetBigFeedbackLogs to complete. Recording happens on each call to
diff --git a/tools/metrics/histograms/metadata/enterprise/enums.xml b/tools/metrics/histograms/metadata/enterprise/enums.xml
index 6958111..7eb54d9 100644
--- a/tools/metrics/histograms/metadata/enterprise/enums.xml
+++ b/tools/metrics/histograms/metadata/enterprise/enums.xml
@@ -489,6 +489,7 @@
   <int value="29" label="FAILURE_UNKNOWN_ERROR"/>
   <int value="30" label="FAILURE_REAUTHZ_POLICY_CHECK_FAILED"/>
   <int value="31" label="FAILURE_NO_COMMON_AUTH_METHOD"/>
+  <int value="32" label="FAILURE_SESSION_POLICIES_CHANGED"/>
 </enum>
 
 <enum name="EnterpriseDeviceManagementStatus">
diff --git a/tools/metrics/histograms/metadata/extensions/enums.xml b/tools/metrics/histograms/metadata/extensions/enums.xml
index dd7fe60..af79deca5 100644
--- a/tools/metrics/histograms/metadata/extensions/enums.xml
+++ b/tools/metrics/histograms/metadata/extensions/enums.xml
@@ -3149,6 +3149,7 @@
   <int value="257" label="kChromeOSManagementAudio"/>
   <int value="258" label="kChromeOSDiagnosticsNetworkInfoForMlab"/>
   <int value="259" label="kAIAssistantOriginTrial"/>
+  <int value="260" label="kExperimentalAiData"/>
 </enum>
 
 <enum name="ExtensionPolicyReinstallReason">
diff --git a/tools/metrics/histograms/metadata/ios/enums.xml b/tools/metrics/histograms/metadata/ios/enums.xml
index f78a094..88dae4d1 100644
--- a/tools/metrics/histograms/metadata/ios/enums.xml
+++ b/tools/metrics/histograms/metadata/ios/enums.xml
@@ -398,6 +398,8 @@
   <int value="13" label="Set Up List Notifications"/>
   <int value="14" label="Placeholder"/>
   <int value="15" label="Price Tracking Promo"/>
+  <int value="16" label="Tips (with Product Image)"/>
+  <int value="17" label="Tips"/>
 </enum>
 
 <enum name="IOSMiniMapConsentOutcome">
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index 241a4ae..6fa01b29 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -1985,6 +1985,15 @@
   </summary>
 </histogram>
 
+<histogram name="IOS.MagicStack.Module.Click.Tips" units="index"
+    expires_after="2025-01-26">
+  <owner>bwwilliams@google.com</owner>
+  <owner>bling-get-set-up@google.com</owner>
+  <summary>
+    Records the rank index of the Tips module when a user clicks on it.
+  </summary>
+</histogram>
+
 <histogram name="IOS.MagicStack.Module.Disabled" enum="IOSMagicStackModuleType"
     expires_after="2025-05-03">
   <owner>thegreenfrog@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/mac/histograms.xml b/tools/metrics/histograms/metadata/mac/histograms.xml
index 69aac656..793c099 100644
--- a/tools/metrics/histograms/metadata/mac/histograms.xml
+++ b/tools/metrics/histograms/metadata/mac/histograms.xml
@@ -104,6 +104,83 @@
   </summary>
 </histogram>
 
+<histogram name="Mac.ProcessRequirement.CurrentProcessValid"
+    enum="BooleanSuccess" expires_after="2025-06-01">
+  <owner>markrowe@chromium.org</owner>
+  <owner>src/base/mac/OWNERS</owner>
+  <summary>
+    The result of validating the current process against a same signing identity
+    process requirement. This histogram is emitted once at browser startup.
+  </summary>
+</histogram>
+
+<histogram name="Mac.ProcessRequirement.FallbackValidationCategory.Result"
+    enum="CodeSigningOSStatus" expires_after="2025-06-01">
+  <owner>markrowe@chromium.org</owner>
+  <owner>src/base/mac/OWNERS</owner>
+  <summary>
+    The result of retrieving the validation category of the current process via
+    the fallback code path. 0 on success, or an`OSStatus` value. This histogram
+    is emitted once at browser startup.
+  </summary>
+</histogram>
+
+<histogram name="Mac.ProcessRequirement.TeamIdentifier.Result" enum="MacErrno"
+    expires_after="2025-06-01">
+  <owner>markrowe@chromium.org</owner>
+  <owner>src/base/mac/OWNERS</owner>
+  <summary>
+    The result of retrieving the team identifier of the current process. 0 on
+    success, the `errno` value from the `csops` system call otherwise. This
+    histogram is emitted once at browser startup.
+  </summary>
+</histogram>
+
+<histogram name="Mac.ProcessRequirement.Timing.{Operation}" units="ms"
+    expires_after="2025-06-01">
+  <owner>markrowe@chromium.org</owner>
+  <owner>src/base/mac/OWNERS</owner>
+  <summary>
+    The time taken to {Operation}. This histogram is emitted once at browser
+    startup.
+  </summary>
+  <token key="Operation">
+    <variant name="BuildSameIdentityRequirement"
+        summary="build a same identity requirement"/>
+    <variant name="ValidateSameIdentity"
+        summary="validate the current process against a same identity
+                 requirement"/>
+  </token>
+</histogram>
+
+<histogram name="Mac.ProcessRequirement.ValidationCategory.Result"
+    enum="MacErrno" expires_after="2025-06-01">
+  <owner>markrowe@chromium.org</owner>
+  <owner>src/base/mac/OWNERS</owner>
+  <summary>
+    The result of retrieving the validation category of the current process. 0
+    on success, the `errno` value from the `csops` system call otherwise. This
+    histogram is emitted once at browser startup.
+  </summary>
+</histogram>
+
+<histogram name="Mac.ProcessRequirement.{Field}.HasExpectedValue"
+    enum="BooleanSuccess" expires_after="2025-06-01">
+  <owner>markrowe@chromium.org</owner>
+  <owner>src/base/mac/OWNERS</owner>
+  <summary>
+    Whether the {Field} retrieved for the current process matched the expected
+    value. This will only be emitted if the {Field} was successfully retrieved.
+    This histogram is emitted once at browser startup.
+  </summary>
+  <token key="Field">
+    <variant name="FallbackValidationCategory"
+        summary="fallback validation category"/>
+    <variant name="TeamIdentifier" summary="team identifier"/>
+    <variant name="ValidationCategory" summary="validation category"/>
+  </token>
+</histogram>
+
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index c925c0b..577ea23 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -6842,6 +6842,16 @@
   </summary>
 </histogram>
 
+<histogram name="Media.VideoResourceUpdater.FrameFormat"
+    enum="VideoPixelFormatUnion" expires_after="2025-10-01">
+  <owner>dalecurtis@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <summary>
+    The format of each VideoFrame passed to VideoResourceUpdater. Recorded prior
+    to resources being obtained for passing to Viz.
+  </summary>
+</histogram>
+
 <histogram name="Media.WatchTime" units="ms" expires_after="2025-03-09">
   <owner>dalecurtis@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/net/enums.xml b/tools/metrics/histograms/metadata/net/enums.xml
index af3f894..022ce9b 100644
--- a/tools/metrics/histograms/metadata/net/enums.xml
+++ b/tools/metrics/histograms/metadata/net/enums.xml
@@ -2431,6 +2431,14 @@
   <int value="9" label="TooBigDictionary"/>
 </enum>
 
+<enum name="SkipCompletionPortOnSuccessOutcome">
+  <int value="0"
+      label="Not all available transport protocols return Installable File
+             System (IFS) handles"/>
+  <int value="1" label="SetFileCompletionNotificationModes failed"/>
+  <int value="2" label="Succeeded"/>
+</enum>
+
 <enum name="SpdyFrameFlowControlState">
   <int value="0" label="Send not stalled"/>
   <int value="1" label="Send stalled by stream"/>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 8d570bed..0fdb888 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -5169,6 +5169,17 @@
   </summary>
 </histogram>
 
+<histogram name="Net.Socket.SkipCompletionPortOnSuccessOutcome"
+    enum="SkipCompletionPortOnSuccessOutcome" expires_after="2025-02-01">
+  <owner>fdoray@chromium.org</owner>
+  <owner>ricea@chromium.org</owner>
+  <summary>
+    The outcome of enabling the FILE_SKIP_COMPLETION_PORT_ON_SUCCESS option on a
+    TcpSocketIoCompletionPortWin. Recorded for 1/1000 attempts to set the
+    option.
+  </summary>
+</histogram>
+
 <histogram name="Net.SocketUnchangeableReceiveBuffer" units="Bytes"
     expires_after="2025-07-01">
   <owner>dschinazi@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 2ff9c7a..a4969a9 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -4124,7 +4124,7 @@
     enum="FeedbackAppContactUserConsentType" expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records whether users allow Google to contact them about the issues. Fires
     when feedback report is being sent.
@@ -4135,7 +4135,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Record the length of description in the textbox. Fires when the feedback
     report is being sent.
@@ -4145,7 +4145,7 @@
 <histogram name="Feedback.ChromeOSApp.Duration.GetBigFeedbackLogs" units="ms"
     expires_after="2024-11-03">
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the elapsed time from the start of fetching Debugd logs via the
     GetBigFeedbackLogs dbus method to when the data has been retrieved. Fires
@@ -4159,7 +4159,7 @@
     expires_after="2025-03-23">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records a summary of the actions the user took before exiting the app. Fires
     when user closes the feedback app, case includes: User quits on search page
@@ -4175,7 +4175,7 @@
 <histogram name="Feedback.ChromeOSApp.GetBigFeedbackLogs.EmptyCount"
     units="logs" expires_after="2025-07-30">
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the number of logs with empty data retrieved from debugd for
     feedback reports. Fires during the process of sending a feedback report on
@@ -4187,7 +4187,7 @@
 <histogram name="Feedback.ChromeOSApp.GetBigFeedbackLogs.NotAvailableCount"
     units="logs" expires_after="2025-07-30">
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the number of logs with not available data retrieved from debugd for
     feedback reports. Fires during the process of sending a feedback report on
@@ -4199,7 +4199,7 @@
 <histogram name="Feedback.ChromeOSApp.GetBigFeedbackLogs.OtherCount"
     units="logs" expires_after="2024-11-03">
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the number of logs with data not empty or not available retrieved
     from debugd for feedback reports. Fires during the process of sending a
@@ -4211,7 +4211,7 @@
 <histogram name="Feedback.ChromeOSApp.GetBigFeedbackLogs.Success"
     enum="Boolean" expires_after="2024-11-03">
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the success status of fetching the feedback logs via debus method
     GetBigFeedbackLogs. Fires during the process of sending a feedback report on
@@ -4223,7 +4223,7 @@
 <histogram name="Feedback.ChromeOSApp.GetFeedbackLogsV2.DBusResult"
     enum="GetFeedbackLogsV2DbusResult" expires_after="2024-11-17">
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the different results of fetching the feedback logs via debus method
     GetFeedbackLogsV2. Fires during the process of sending a feedback report on
@@ -4236,7 +4236,7 @@
     enum="FeedbackAppHelpContentOutcome" expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Record the outcome of checking help contents. Fires when user leaves the
     search page by clicking continue or by clicking x on top right to close the
@@ -4248,7 +4248,7 @@
     units="numbers" expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the number of help content results returned in each search. Fires
     when user clicks and views the help content on search page.
@@ -4259,7 +4259,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records whether the email is included when the feedback report is submitted.
     Fires when user's feedback report is being sent.
@@ -4270,7 +4270,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records whether attached file is included when the feedback report is
     submitted. Fires when user's feedback report is being sent.
@@ -4281,7 +4281,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records whether the screenshot is included when the feedback report is
     submitted. Fires when a user's feedback report is successfully sent.
@@ -4292,7 +4292,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records whether the system and information is included when the feedback
     report is submitted. Fires when a user's feedback report is being sent.
@@ -4303,7 +4303,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records whether the page url is included when the feedback report is
     submitted. Fires when user's feedback report is being sent.
@@ -4314,7 +4314,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the duration that the Feedback App is open. Emitted when the app is
     closed.
@@ -4325,7 +4325,7 @@
     enum="FeedbackAppPostSubmitAction" expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the first action that the users take after sending feedback. Fires
     on the first action taken on post submit page after the user has sent the
@@ -4337,7 +4337,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the duration that the Feedback App is open on {FeedbackAppPage}
     page. Fires when the user closes the app or starts new report after feedback
@@ -4354,7 +4354,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Record number of times the user viewed the help content. Fires when user
     clicks help content on search page.
@@ -4365,7 +4365,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Record number of times the user viewed the attached image. Fires when user
     clicks the thumbnail and views the larger image.
@@ -4376,7 +4376,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Record number of times the user viewed the metrics. Fires when user clicks
     metrics link and views metrics.
@@ -4387,7 +4387,7 @@
     expires_after="2025-01-05">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Record number of times the user viewed the screenshot. Fires when user
     clicks the thumbnail on and views the larger screenshot.
@@ -4398,7 +4398,7 @@
     expires_after="2025-07-30">
   <owner>longbowei@google.com</owner>
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Record number of times the user viewed the system and app information. Fires
     when system and app info link is clicked.
@@ -4409,7 +4409,7 @@
     expires_after="2024-11-03">
   <owner>xiangdongkong@google.com</owner>
   <owner>fernandex@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the elapsed time from the start of fetching system information to
     when the data has been retrieved. Fires during the process of sending a
@@ -4421,7 +4421,7 @@
     expires_after="2025-07-30">
   <owner>xiangdongkong@google.com</owner>
   <owner>fernandex@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the elapsed time from when the feedback app window is opened to when
     the backend receives the send report request submitted by a user. Fires
@@ -4433,7 +4433,7 @@
 <histogram name="Feedback.Duration.FormSubmitToConfirmation" units="ms"
     expires_after="2022-08-21">
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the elapsed time from when the backend receives the send report
     request submitted by a user to when it starts to send confirmation back to
@@ -4445,7 +4445,7 @@
 <histogram name="Feedback.Duration.FormSubmitToSendQueue" units="ms"
     expires_after="2025-07-30">
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the elapsed time from when the backend receives the send report
     request submitted by a user and the report has been added to the send queue.
@@ -4564,7 +4564,7 @@
 <histogram name="Feedback.ReportSending.Online" enum="Boolean"
     expires_after="2025-01-21">
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records whether the user is online when the feedback report was submitted.
   </summary>
@@ -4573,7 +4573,7 @@
 <histogram name="Feedback.ReportSending.Result"
     enum="FeedbackReportSendingResult" expires_after="2025-03-23">
   <owner>xiangdongkong@google.com</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>
     Records the feedback report sending states to help track the success rate of
     feedback report offline submission.
@@ -4583,7 +4583,7 @@
 <histogram name="Feedback.RequestSource" enum="FeedbackSource"
     expires_after="2025-03-09">
   <owner>afakhry@chromium.org</owner>
-  <owner>cros-feedback-app@google.com</owner>
+  <owner>cros-device-enablement@google.com</owner>
   <summary>Records the source that requested showing the feedback app.</summary>
 </histogram>
 
@@ -10260,6 +10260,19 @@
   <token key="WebUIName" variants="TopChromeWebUIName"/>
 </histogram>
 
+<histogram name="WebUI.TopChrome.RequestToLCP{WebUIName}" units="ms"
+    expires_after="2025-01-26">
+  <owner>kerenzhu@chromium.org</owner>
+  <owner>chrome-webui-for-features@google.com</owner>
+  <summary>
+    Measures the time elapsed from the initiation of a WebUI request to its
+    largest contentful paint. This metric applies to the main frame documents of
+    top-chrome WebUI pages, such as side-panel content. The request initiation
+    is marked by the call to WebUIContentsPreloadManager::Request().
+  </summary>
+  <token key="WebUIName" variants="TopChromeWebUIName"/>
+</histogram>
+
 <histogram name="WebUI.WebUIURLLoaderFactory.URLRequestLoadTime" units="ms"
     expires_after="2022-10-30">
   <owner>dpapad@chromium.org</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index c1652b1a..c0a7460 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "perfetto-luci-artifacts/9eecd90b2cf99639904f5a5095d293ea4252c063/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "066fdeeadeb913dc99cd6cb19916237ccf804d49",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/077bb360edecb33551e88c8097035e4b8cef245b/trace_processor_shell.exe"
+            "hash": "6f8fb9c6cde467bd7e2cf30c8b6f4dc87123f44f",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/136de5ccd7163261020064db944bb07bf5f5cf12/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "bb963e5488d9a76861165256126830c7ae523733",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/9eecd90b2cf99639904f5a5095d293ea4252c063/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "a970025e2d93c368de68982b257f43c28cd3d4c5",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/c6494911fdbe08d2df0057eb94034dca930e3d68/trace_processor_shell"
+            "hash": "3887f906fff1c425fc59c5ca2369772ea67ac5ca",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/136de5ccd7163261020064db944bb07bf5f5cf12/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/symsrc/OWNERS b/tools/symsrc/OWNERS
index 6b72bfb..8d63a285 100644
--- a/tools/symsrc/OWNERS
+++ b/tools/symsrc/OWNERS
@@ -1,2 +1,2 @@
-brucedawson@chromium.org
+jessemckenna@google.com
 sebmarchand@chromium.org
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 4542d4b..e3600c421 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -39,7 +39,7 @@
  <item id="chrome_apps_socket_api" added_in_milestone="65" content_hash_code="07ca7795" os_list="linux,windows,chromeos" file_path="extensions/browser/api/socket/socket.cc" />
  <item id="chrome_cart_discounts_lookup" added_in_milestone="92" content_hash_code="05b3e421" os_list="linux,windows,chromeos" file_path="chrome/browser/cart/cart_discount_fetcher.cc" />
  <item id="chrome_cart_get_discounted_link" added_in_milestone="92" content_hash_code="052260e0" os_list="linux,windows,chromeos" file_path="chrome/browser/cart/cart_discount_link_fetcher.cc" />
- <item id="chrome_feedback_report_app" added_in_milestone="62" content_hash_code="050db812" os_list="linux,windows,chromeos,android" file_path="components/feedback/feedback_uploader.cc" />
+ <item id="chrome_feedback_report_app" added_in_milestone="62" content_hash_code="05f41c5b" os_list="linux,windows,chromeos,android" file_path="components/feedback/feedback_uploader.cc" />
  <item id="chrome_variations_service" added_in_milestone="62" content_hash_code="00f59481" os_list="linux,windows,chromeos,android" file_path="components/variations/service/variations_service.cc" />
  <item id="client_download_request" added_in_milestone="62" content_hash_code="04d329eb" os_list="linux,windows,chromeos" file_path="chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc" />
  <item id="content_hash_verification_job" added_in_milestone="62" content_hash_code="079fc9db" os_list="linux,windows,chromeos" file_path="extensions/browser/content_hash_fetcher.cc" />
@@ -308,7 +308,7 @@
  <item id="printing_oauth2_http_exchange" added_in_milestone="101" type="completing" content_hash_code="079d0e2f" os_list="chromeos" semantics_fields="1,3,5,6" policy_fields="-1,3,5" file_path="chrome/browser/ash/printing/oauth2/http_exchange.cc" />
  <item id="printing_oauth2_first_token_request" added_in_milestone="102" type="partial" second_id="printing_oauth2_http_exchange" content_hash_code="00850a77" os_list="chromeos" semantics_fields="2,4" file_path="chrome/browser/ash/printing/oauth2/authorization_server_session.cc" />
  <item id="printing_oauth2_next_token_request" added_in_milestone="102" type="partial" second_id="printing_oauth2_http_exchange" content_hash_code="002fbbfd" os_list="chromeos" semantics_fields="2,4" file_path="chrome/browser/ash/printing/oauth2/authorization_server_session.cc" />
- <item id="help_content_provider" added_in_milestone="102" content_hash_code="0667db51" os_list="chromeos" file_path="ash/webui/os_feedback_ui/backend/help_content_provider.cc" />
+ <item id="help_content_provider" added_in_milestone="102" content_hash_code="02b6c880" os_list="chromeos" file_path="ash/webui/os_feedback_ui/backend/help_content_provider.cc" />
  <item id="quick_answers_spellchecker" added_in_milestone="102" content_hash_code="022892f0" os_list="chromeos" file_path="chromeos/components/quick_answers/utils/spell_check_language.cc" />
  <item id="printing_oauth2_token_exchange_request" added_in_milestone="102" type="partial" second_id="printing_oauth2_http_exchange" content_hash_code="059df373" os_list="chromeos" semantics_fields="2,4" file_path="chrome/browser/ash/printing/oauth2/ipp_endpoint_token_fetcher.cc" />
  <item id="fedcm_account_profile_image_fetcher" added_in_milestone="102" content_hash_code="01d6ba52" os_list="linux,windows,chromeos" file_path="chrome/browser/ui/views/webid/account_selection_view_base.cc" />
diff --git a/ui/accessibility/accessibility_features.cc b/ui/accessibility/accessibility_features.cc
index 262896e..d33ee2e8f 100644
--- a/ui/accessibility/accessibility_features.cc
+++ b/ui/accessibility/accessibility_features.cc
@@ -370,7 +370,7 @@
 
 BASE_FEATURE(kReadAnythingReadAloudAutomaticWordHighlighting,
              "ReadAnythingReadAloudAutomaticWordHighlighting",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 bool IsReadAnythingReadAloudAutomaticWordHighlightingEnabled() {
   return base::FeatureList::IsEnabled(::features::kReadAnythingReadAloud) &&
          base::FeatureList::IsEnabled(
diff --git a/ui/base/clipboard/clipboard_ios.mm b/ui/base/clipboard/clipboard_ios.mm
index ab4edf3..20dd7783 100644
--- a/ui/base/clipboard/clipboard_ios.mm
+++ b/ui/base/clipboard/clipboard_ios.mm
@@ -137,10 +137,7 @@
   NSData* data = GetDataWithTypeFromPasteboard(
       GetPasteboard(), (NSString*)kUTTypeChromiumDataTransferCustomData);
   if (data) {
-    ReadCustomDataTypes(
-        base::span(reinterpret_cast<const uint8_t*>([data bytes]),
-                   [data length]),
-        types);
+    ReadCustomDataTypes(base::apple::NSDataToSpan(data), types);
   }
 }
 
@@ -274,10 +271,8 @@
   NSData* data = GetDataWithTypeFromPasteboard(
       GetPasteboard(), (NSString*)kUTTypeChromiumDataTransferCustomData);
   if (data) {
-    if (std::optional<std::u16string> maybe_result = ReadCustomDataForType(
-            base::span(reinterpret_cast<const uint8_t*>([data bytes]),
-                       [data length]),
-            type);
+    if (std::optional<std::u16string> maybe_result =
+            ReadCustomDataForType(base::apple::NSDataToSpan(data), type);
         maybe_result) {
       *result = std::move(*maybe_result);
     }
@@ -350,7 +345,7 @@
   NSData* data =
       GetDataWithTypeFromPasteboard(GetPasteboard(), format.ToNSString());
   if (data) {
-    result->assign(static_cast<const char*>([data bytes]), [data length]);
+    result->assign(base::as_string_view(base::apple::NSDataToSpan(data)));
   }
 }
 
@@ -493,9 +488,8 @@
     return std::vector<uint8_t>();
   }
 
-  const uint8_t* bytes = (const uint8_t*)png_data.bytes;
-  std::vector<uint8_t> png(bytes, bytes + png_data.length);
-  return png;
+  auto png_span = base::apple::NSDataToSpan(png_data);
+  return std::vector<uint8_t>(png_span.begin(), png_span.end());
 }
 
 }  // namespace ui
diff --git a/ui/base/test/ios/ui_image_test_utils.mm b/ui/base/test/ios/ui_image_test_utils.mm
index 80d2f727..f281ce52 100644
--- a/ui/base/test/ios/ui_image_test_utils.mm
+++ b/ui/base/test/ios/ui_image_test_utils.mm
@@ -4,6 +4,7 @@
 
 #include "ui/base/test/ios/ui_image_test_utils.h"
 
+#include "base/apple/foundation_util.h"
 #include "base/apple/scoped_cftyperef.h"
 
 namespace ui::test::uiimage_utils {
@@ -32,26 +33,23 @@
 }
 
 bool UIImagesAreEqual(UIImage* image_1, UIImage* image_2) {
-  if (image_1 == image_2)
+  if (image_1 == image_2) {
     return true;
+  }
 
-  if (!CGSizeEqualToSize(image_1.size, image_2.size))
+  if (!CGSizeEqualToSize(image_1.size, image_2.size)) {
     return false;
+  }
 
   base::apple::ScopedCFTypeRef<CFDataRef> data_ref_1(
       CGDataProviderCopyData(CGImageGetDataProvider(image_1.CGImage)));
   base::apple::ScopedCFTypeRef<CFDataRef> data_ref_2(
       CGDataProviderCopyData(CGImageGetDataProvider(image_2.CGImage)));
-  CFIndex length_1 = CFDataGetLength(data_ref_1.get());
-  CFIndex length_2 = CFDataGetLength(data_ref_2.get());
-  if (length_1 != length_2) {
-    return false;
-  }
-  const UInt8* ptr_1 = CFDataGetBytePtr(data_ref_1.get());
-  const UInt8* ptr_2 = CFDataGetBytePtr(data_ref_2.get());
 
-  // memcmp returns 0 if length is 0.
-  return memcmp(ptr_1, ptr_2, length_1) == 0;
+  auto span_1 = base::apple::CFDataToSpan(data_ref_1.get());
+  auto span_2 = base::apple::CFDataToSpan(data_ref_2.get());
+
+  return span_1 == span_2;
 }
 
 }  // namespace ui::test::uiimage_utils
diff --git a/ui/base/win/OWNERS b/ui/base/win/OWNERS
index 8adb02e..9741b79 100644
--- a/ui/base/win/OWNERS
+++ b/ui/base/win/OWNERS
@@ -1,5 +1,5 @@
-brucedawson@chromium.org
 davidbienvenu@chromium.org
+jessemckenna@google.com
 robliao@chromium.org
 sky@chromium.org
 
diff --git a/ui/display/mac/ca_display_link_mac.mm b/ui/display/mac/ca_display_link_mac.mm
index ad782e7..32d11d5 100644
--- a/ui/display/mac/ca_display_link_mac.mm
+++ b/ui/display/mac/ca_display_link_mac.mm
@@ -253,6 +253,12 @@
                          .maximum = refresh_rate,
                          .preferred = refresh_rate};
 
+    // This display link interface requires the task executor of the current
+    // thread (CrGpuMain or VizCompositorThread) to run with
+    // MessagePumpType::NS_RUNLOOP. CADisplayLinkTarget and display_link are
+    // NSObject, and MessagePumpType::DEFAULT does not support system work.
+    // There will be no callbacks (CADisplayLinkTarget::step()) at all if
+    // MessagePumpType NS_RUNLOOP is not chosen during thread initialization.
     [objc_state->display_link addToRunLoop:NSRunLoop.currentRunLoop
                                    forMode:NSDefaultRunLoopMode];
 
diff --git a/ui/gfx/vector_icon_types.h b/ui/gfx/vector_icon_types.h
index ebeabd6..a2ae9106 100644
--- a/ui/gfx/vector_icon_types.h
+++ b/ui/gfx/vector_icon_types.h
@@ -5,8 +5,7 @@
 #ifndef UI_GFX_VECTOR_ICON_TYPES_H_
 #define UI_GFX_VECTOR_ICON_TYPES_H_
 
-#include "base/containers/span.h"
-#include "base/memory/raw_ptr_exclusion.h"
+#include "base/memory/raw_span.h"
 #include "third_party/skia/include/core/SkScalar.h"
 #include "ui/gfx/animation/tween.h"
 
@@ -97,11 +96,7 @@
   VectorIconRep(const VectorIconRep&) = delete;
   VectorIconRep& operator=(const VectorIconRep&) = delete;
 
-  // RAW_PTR_EXCLUSION: #global-scope
-  // TODO(crbug.com/363264995): Convert to base::raw_span once that no longer
-  // results in the type being marked as a complex type since that prevents
-  // initialization of the type in constexpr contexts.
-  RAW_PTR_EXCLUSION base::span<const PathElement> path;
+  base::raw_span<const PathElement> path;
 };
 
 // A vector icon that stores one or more representations to be used for various
@@ -118,11 +113,7 @@
 
   bool is_empty() const { return reps.empty(); }
 
-  // RAW_PTR_EXCLUSION: #global-scope
-  // TODO(crbug.com/363264995): Convert to base::raw_span once that no longer
-  // results in the type being marked as a complex type since that prevents
-  // initialization of the type in constexpr contexts.
-  RAW_PTR_EXCLUSION base::span<const VectorIconRep> reps;
+  base::raw_span<const VectorIconRep> reps;
 
   // A human-readable name, useful for debugging, derived from the name of the
   // icon file. This can also be used as an identifier, but vector icon targets
diff --git a/ui/gl/gl_context.cc b/ui/gl/gl_context.cc
index db869f35..f7c5fa1 100644
--- a/ui/gl/gl_context.cc
+++ b/ui/gl/gl_context.cc
@@ -413,9 +413,7 @@
   DCHECK(!has_called_on_destory_);
   has_called_on_destory_ = true;
 
-  for (auto& obs : observer_list_) {
-    obs.OnGLContextWillDestroy(this);
-  }
+  observer_list_.Notify(&GLContextObserver::OnGLContextWillDestroy, this);
 }
 
 std::unique_ptr<gl::GLVersionInfo> GLContext::GenerateGLVersionInfo() {
@@ -426,9 +424,7 @@
 void GLContext::MarkContextLost() {
   context_lost_ = true;
 
-  for (auto& obs : observer_list_) {
-    obs.OnGLContextLost(this);
-  }
+  observer_list_.Notify(&GLContextObserver::OnGLContextLost, this);
 }
 
 void GLContext::SetCurrent(GLSurface* surface) {
diff --git a/ui/gl/gpu_switching_manager.cc b/ui/gl/gpu_switching_manager.cc
index 4ab9867..9f97dc8 100644
--- a/ui/gl/gpu_switching_manager.cc
+++ b/ui/gl/gpu_switching_manager.cc
@@ -27,23 +27,20 @@
 
 void GpuSwitchingManager::NotifyGpuSwitched(
     gl::GpuPreference active_gpu_heuristic) {
-  for (GpuSwitchingObserver& observer : observer_list_)
-    observer.OnGpuSwitched(active_gpu_heuristic);
+  observer_list_.Notify(&GpuSwitchingObserver::OnGpuSwitched,
+                        active_gpu_heuristic);
 }
 
 void GpuSwitchingManager::NotifyDisplayAdded() {
-  for (GpuSwitchingObserver& observer : observer_list_)
-    observer.OnDisplayAdded();
+  observer_list_.Notify(&GpuSwitchingObserver::OnDisplayAdded);
 }
 
 void GpuSwitchingManager::NotifyDisplayRemoved() {
-  for (GpuSwitchingObserver& observer : observer_list_)
-    observer.OnDisplayRemoved();
+  observer_list_.Notify(&GpuSwitchingObserver::OnDisplayRemoved);
 }
 
 void GpuSwitchingManager::NotifyDisplayMetricsChanged() {
-  for (GpuSwitchingObserver& observer : observer_list_)
-    observer.OnDisplayMetricsChanged();
+  observer_list_.Notify(&GpuSwitchingObserver::OnDisplayMetricsChanged);
 }
 
 }  // namespace ui
diff --git a/ui/gl/vsync_thread_win.cc b/ui/gl/vsync_thread_win.cc
index 8031c4d..f3e35d77 100644
--- a/ui/gl/vsync_thread_win.cc
+++ b/ui/gl/vsync_thread_win.cc
@@ -299,9 +299,7 @@
   PostTaskIfNeeded();
 
   const base::TimeTicks vsync_time = base::TimeTicks::Now();
-  for (VSyncObserver& obs : observers_) {
-    obs.OnVSync(vsync_time, vsync_interval);
-  }
+  observers_.Notify(&VSyncObserver::OnVSync, vsync_time, vsync_interval);
 }
 
 }  // namespace gl
diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc
index 5cf15029..69fdf30 100644
--- a/ui/gtk/gtk_ui.cc
+++ b/ui/gtk/gtk_ui.cc
@@ -175,12 +175,15 @@
   }
 }
 
+int GetXftDpi() {
+  int dpi = -1;
+  g_object_get(gtk_settings_get_default(), "gtk-xft-dpi", &dpi, nullptr);
+  return dpi < 0 ? 0 : dpi;
+}
+
 double FontScale() {
   double resolution = 0;
-  if (GtkCheckVersion(4)) {
-    auto* settings = gtk_settings_get_default();
-    int dpi = 0;
-    g_object_get(settings, "gtk-xft-dpi", &dpi, nullptr);
+  if (const int dpi = GetXftDpi()) {
     resolution = dpi / 1024.0;
   } else {
     GdkScreen* screen = gdk_screen_get_default();
@@ -260,8 +263,8 @@
   connect(settings, "notify::gtk-enable-animations",
           &GtkUi::OnEnableAnimationsChanged);
 
-  // Listen for DPI changes.
-  if (GtkCheckVersion(4)) {
+  // Listen for DPI changes, if supported.
+  if (GetXftDpi() > 0) {
     connect(settings, "notify::gtk-xft-dpi", &GtkUi::OnGtkXftDpiChanged);
   } else {
     GdkScreen* screen = gdk_screen_get_default();
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index d58094e..3487141 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -995,7 +995,7 @@
   // ui scale with the current font scale, the pixel size must be scaled
   // accordingly. Both scale and bounds might get updated later in the window
   // configuration process.
-  state.ui_scale = connection_->window_manager()->font_scale();
+  state.ui_scale = connection_->window_manager()->GetFontScale();
   state.size_px = gfx::ScaleToEnclosingRectIgnoringError(
                       gfx::Rect(state.bounds_dip.size()),
                       state.window_scale * state.ui_scale)
@@ -1444,12 +1444,10 @@
   // ui_scale determines how the window content, ie: UI, will be laid out and
   // sized. As of now, it is retrieved from the 'font scaling factor' system
   // setting (aka: text scaling factor).
-  const float new_ui_scale = connection_->window_manager()->font_scale();
+  const float new_ui_scale = connection_->window_manager()->GetFontScale();
   state.bounds_dip = gfx::ScaleToEnclosingRectIgnoringError(
       state.bounds_dip, state.ui_scale / new_ui_scale);
   state.ui_scale = new_ui_scale;
-  CHECK(connection_->IsUiScaleEnabled() || state.ui_scale == 1.0f)
-      << state.ui_scale;
 
   // Adjust state values if necessary.
   state.bounds_dip = AdjustBoundsToConstraintsDIP(state.bounds_dip);
diff --git a/ui/ozone/platform/wayland/host/wayland_window_manager.cc b/ui/ozone/platform/wayland/host/wayland_window_manager.cc
index cfddaf8..059a55d 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_manager.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_manager.cc
@@ -4,6 +4,9 @@
 
 #include "ui/ozone/platform/wayland/host/wayland_window_manager.h"
 
+#include <algorithm>
+
+#include "base/check.h"
 #include "base/observer_list.h"
 #include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
@@ -283,4 +286,11 @@
   }
 }
 
+float WaylandWindowManager::GetFontScale() const {
+  constexpr float kMinFontScale = 0.5f;
+  constexpr float kMaxFontScale = 3.0f;
+  CHECK(connection_->IsUiScaleEnabled() || font_scale_ == 1.0f) << font_scale_;
+  return std::clamp(font_scale_, kMinFontScale, kMaxFontScale);
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_window_manager.h b/ui/ozone/platform/wayland/host/wayland_window_manager.h
index 5a97f05a..fb7442a 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_manager.h
+++ b/ui/ozone/platform/wayland/host/wayland_window_manager.h
@@ -115,8 +115,8 @@
   // Creates a new unique gfx::AcceleratedWidget.
   gfx::AcceleratedWidget AllocateAcceleratedWidget();
 
-  float font_scale() const { return font_scale_; }
   void SetFontScale(float new_font_scale);
+  float GetFontScale() const;
 
   void DumpState(std::ostream& out) const;
 
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index 84fd51f..afbddb87 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -6022,6 +6022,27 @@
   EXPECT_EQ(menu_window->root_surface()->state_.buffer_scale_float, 1.0f);
 }
 
+TEST_P(PerSurfaceScaleWaylandWindowTest, UiScale_SanitizeFontScale) {
+  base::test::ScopedFeatureList enable_ui_scaling(features::kWaylandUiScale);
+  ASSERT_TRUE(connection_->IsUiScaleEnabled());
+
+  auto test_font_scale = [&](float requested_font_scale,
+                             float sanitized_font_scale) {
+    EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
+    connection_->window_manager()->SetFontScale(requested_font_scale);
+    Mock::VerifyAndClearExpectations(&delegate_);
+    WaylandTestBase::SyncDisplay();
+    EXPECT_EQ(sanitized_font_scale, window_->applied_state().ui_scale);
+    EXPECT_EQ(1.0f, window_->applied_state().window_scale);
+  };
+
+  // Set arbitrary font scale values and verify expectations.
+  test_font_scale(20.0f, 3.0f);
+  test_font_scale(0.10f, 0.5f);
+  test_font_scale(1.5f, 1.5f);
+  test_font_scale(-1.0f, 0.5f);
+}
+
 #if !BUILDFLAG(IS_CHROMEOS_LACROS)
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandWindowTest,
diff --git a/ui/views/controls/button/label_button.cc b/ui/views/controls/button/label_button.cc
index 8fcf757..5b675ae 100644
--- a/ui/views/controls/button/label_button.cc
+++ b/ui/views/controls/button/label_button.cc
@@ -452,10 +452,6 @@
   return layouts;
 }
 
-void LabelButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  Button::GetAccessibleNodeData(node_data);
-}
-
 ui::NativeTheme::Part LabelButton::GetThemePart() const {
   return ui::NativeTheme::kPushButton;
 }
diff --git a/ui/views/controls/button/label_button.h b/ui/views/controls/button/label_button.h
index 414087e0..7586a6f 100644
--- a/ui/views/controls/button/label_button.h
+++ b/ui/views/controls/button/label_button.h
@@ -161,7 +161,6 @@
   gfx::Size CalculatePreferredSize(
       const SizeBounds& available_size) const override;
   gfx::Size GetMinimumSize() const override;
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   void AddLayerToRegion(ui::Layer* new_layer,
                         views::LayerRegion region) override;
   void RemoveLayerFromRegions(ui::Layer* old_layer) override;
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_manager_v2.mojom b/ui/webui/resources/cr_components/certificate_manager/certificate_manager_v2.mojom
index 02b29b73..5cdfa9ed 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_manager_v2.mojom
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_manager_v2.mojom
@@ -132,6 +132,9 @@
   [EnableIf=native_cert_management]
   ShowNativeManageCertificates();
 
+  // Set whether to use the OS imported certs or not.
+  [EnableIfNot=is_chromeos]
+  SetIncludeSystemTrustStore(bool include);
 };
 
 // Calls from C++ -> TS (Browser -> Renderer).
diff --git a/ui/webui/resources/cr_components/certificate_manager/local_certs_section_v2.html b/ui/webui/resources/cr_components/certificate_manager/local_certs_section_v2.html
index c3474b29..90a00c2 100644
--- a/ui/webui/resources/cr_components/certificate_manager/local_certs_section_v2.html
+++ b/ui/webui/resources/cr_components/certificate_manager/local_certs_section_v2.html
@@ -26,11 +26,11 @@
             [[numSystemCertsString_]]
           </div>
         </div>
-        <!-- TODO(crbug.com/40928765): Have this toggle control whether the OS
-          imported certs are used.  -->
         <cr-toggle id="importOsCerts"
             aria-label="Toggle operating system certificate imports"
-            checked="[[importOsCertsEnabled_]]" disabled$="[[importOsCertsEnabledManaged_]]">
+            checked="[[importOsCertsEnabled_]]"
+            disabled$="[[importOsCertsEnabledManaged_]]"
+            on-change="onOsCertsToggleChanged_"></cr-toggle>
         </cr-toggle>
 
         <cr-icon id="importOsCertsManagedIcon" icon="cr:domain"
diff --git a/ui/webui/resources/cr_components/certificate_manager/local_certs_section_v2.ts b/ui/webui/resources/cr_components/certificate_manager/local_certs_section_v2.ts
index d497cb66..5154e37 100644
--- a/ui/webui/resources/cr_components/certificate_manager/local_certs_section_v2.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/local_certs_section_v2.ts
@@ -190,6 +190,13 @@
     proxy.handler.showNativeManageCertificates();
   }
   // </if>
+
+  // <if expr="not is_chromeos">
+  private onOsCertsToggleChanged_(e: CustomEvent<boolean>) {
+    const proxy = CertificatesV2BrowserProxy.getInstance();
+    proxy.handler.setIncludeSystemTrustStore(e.detail);
+  }
+  // </if>
 }
 
 declare global {
diff --git a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html
index eb2c864..a405d69 100644
--- a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html
+++ b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html
@@ -277,7 +277,7 @@
         [[getAnswerOrError_(searchResult_.*)]]
       </div>
       <a class="answer-source" hidden="[[!answerSource_]]"
-          href="[[answerSource_.url.url]]" target="_blank">
+          href="[[getAnswerSourceUrl_(answerSource_)]]" target="_blank">
         <div>Source:</div>
         <div class="favicon"
             style$="background-image: [[getFavicon_(answerSource_)]]"></div>
diff --git a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.ts b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.ts
index 3036369..19d11e0 100644
--- a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.ts
+++ b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.ts
@@ -200,6 +200,20 @@
     }
   }
 
+  private getAnswerSourceUrl_(): string|undefined {
+    if (!this.answerSource_) {
+      return undefined;
+    }
+    const sourceUrl = new URL(this.answerSource_.url.url);
+    const textDirectives = this.answerSource_.answerData?.answerTextDirectives;
+    if (textDirectives && textDirectives.length > 0) {
+      // Only the first directive is used for now until there's a way to show
+      // multiple links in the UI.
+      sourceUrl.hash = `:~:text=${encodeURIComponent(textDirectives[0])}`;
+    }
+    return sourceUrl.toString();
+  }
+
   private getFavicon_(item: SearchResultItem|undefined): string {
     return getFaviconForPageURL(
         item?.url.url || '', /*isSyncedUrlForHistoryUi=*/ true);