diff --git a/.gitmodules b/.gitmodules
index f42f26b..7525b03 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -669,10 +669,6 @@
 	path = chrome/test/media_router/internal
 	url = https://chrome-internal.googlesource.com/chrome/test/media_router/internal
 	gclient-condition = checkout_src_internal
-[submodule "chrome/test/python_tests"]
-	path = chrome/test/python_tests
-	url = https://chrome-internal.googlesource.com/chrome/test/python_tests
-	gclient-condition = checkout_src_internal
 [submodule "chrome/tools/memory"]
 	path = chrome/tools/memory
 	url = https://chrome-internal.googlesource.com/chrome/tools/memory
diff --git a/BUILD.gn b/BUILD.gn
index 56a5419..357f711 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -390,7 +390,7 @@
         "//third_party/android_build_tools/lint:custom_lint_java",
         "//third_party/androidx_javascriptengine",
         "//third_party/catapult/devil",
-        "//third_party/jni_zero:jni_generator_tests",
+        "//third_party/jni_zero:jni_zero_tests",
         "//third_party/r8:custom_d8_java",
         "//tools/android:android_tools",
         "//tools/android:memconsumer",
diff --git a/DEPS b/DEPS
index f02798e..4e9aca0 100644
--- a/DEPS
+++ b/DEPS
@@ -253,7 +253,7 @@
   # luci-go CIPD package version.
   # Make sure the revision is uploaded by infra-packagers builder.
   # https://ci.chromium.org/p/infra-internal/g/infra-packagers/console
-  'luci_go': 'git_revision:06dc7a1f2eeb1d095f7876799458328a44438df1',
+  'luci_go': 'git_revision:239be4fd8499df782db6bddb0f55832bf4f01307',
 
   # This can be overridden, e.g. with custom_vars, to build clang from HEAD
   # instead of downloading the prebuilt pinned revision.
@@ -308,19 +308,19 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': '18e908ec2a27af947b47b16f90a2872121f332c7',
+  'src_internal_revision': '86281eb09ff42ce162e85fc16de5f421c9fb0326',
   # 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': 'df6d08abb447b780c4075cd56f87582b829d2948',
+  'skia_revision': '28579a88aa9c12ef23c1d3ab798aba40f85ea0d8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'ac1a0c9ec71c6c9f7693767dc045f5494eb5d563',
+  'v8_revision': 'de7ce8021831d4f3c4e49336623f25b2fc406fbc',
   # 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': '926334570f2c209b30fd74cc6c4c34e24b9b8e41',
+  'angle_revision': 'e41286e1092c950b64a6e6c6a46eb7c716d8a3a9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -328,7 +328,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '0e4ff8cd885348e2ac01ea640f666634e7a852a7',
+  'pdfium_revision': 'f0dc864fb19ec85c9496387f1e806b14f5e6d999',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -359,7 +359,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling NaCl
   # and whatever else without interference from each other.
-  'nacl_revision': '99f2552944a668f9e7f61b8d8cae77b72d63a9b1',
+  'nacl_revision': '0253008929d3687acd30e9f618d9a14e88b81bf4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -383,11 +383,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '6e7b54bc42e32e640cf79e1ae4af0716bac2742c',
+  'catapult_revision': '24c482ad9c93a7e6504c70ed8a7f1716543c8371',
   # 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': 'd64e9e23c59862c4c7fd4a79bb770934dbce6e64',
+  'chromium_variations_revision': '4958ce329838edce2c9ddd42f8a0e1cfbaf6ec35',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -427,7 +427,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '1c970ba163189abfd5e0df373cdd57c0595e3be6',
+  'dawn_revision': 'ac32331dc38a0d78bc2a7fd0e89a8e30db10d4ed',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -459,7 +459,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': 'f788b891908dd93274918e5b919e09136f3f1efd',
+  'nearby_revision': '78505e91f7e054444030aec55110893b4fc62500',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -827,12 +827,12 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'd1f7d3377bd9fd9b5fb158dd6ae77f92498ae428',
+    '682118afe56ac594ac2a4343274a9bed1dac8710',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'b70b8d9538282099b376bab14f18d63de1aa540a',
+    'url': Var('chromium_git') + '/website.git' + '@' + '446e54b44987857f3ef2f164d524e7d79dba64cc',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -1157,7 +1157,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '9369805bd6c6b5f59a92b3a4106eb691594eb06b',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '004355d6885f0d4735fea67a97761604c85a9940',
       'condition': 'checkout_chromeos',
   },
 
@@ -1192,13 +1192,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'c41b0affa0aacd0e8b332477e3f46b579d7672b6',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '7894b0d6811036f55f472784d8dd86640450ac41',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '1ebb03b579ea72f1269fbd72f71067593aadd274',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '7941d991440a39b32121ef850dbed0f815cff41c',
     'condition': 'checkout_src_internal',
   },
 
@@ -1243,7 +1243,7 @@
   },
 
   'src/third_party/fp16/src':
-    Var('chromium_git') + '/external/github.com/Maratyszcza/FP16.git' + '@' + '0a92994d729ff76a58f692d3028ca1b64b145d91',
+    Var('chromium_git') + '/external/github.com/Maratyszcza/FP16.git' + '@' + '581ac1c79dd9d9f6f4e8b2934e7a55c7becf0799',
 
   'src/third_party/gemmlowp/src':
     Var('chromium_git') + '/external/github.com/google/gemmlowp.git' + '@' + '13d57703abca3005d97b19df1f2db731607a7dc2',
@@ -1531,7 +1531,7 @@
   },
 
   'src/third_party/libvpx/source/libvpx':
-    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '6445da1b40da7967ccaee23d1ac46d5c3981b89c',
+    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '8762f5efb2917765316a198e6713f0bc93b07c9b',
 
   'src/third_party/libwebm/source':
     Var('chromium_git') + '/webm/libwebm.git' + '@' + 'e4fbea0c9751ae8aa86629b197a28d8276a2b0da',
@@ -1652,7 +1652,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '09a4f3ec842a8932341b195c5b01e141c8a16eb7',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + 'd8a6ddbcc09d44d27c9c0aa114e5d63525872089',
+    Var('chromium_git') + '/openscreen' + '@' + '624f91b189a5a6fd35b1d3c43c60678c1f111dff',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '95fe35ffb383710a6e0567e958ead9a3b66e930c',
@@ -1663,7 +1663,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'ffad8c926639b3ee7204de0743cfd52b40d9b868',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'b1676f92d878b5bed2dbe493a9cbb9125a9ec2d5',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '8ef97ff3b7332e38e61b347a2fbed425a4617151',
@@ -1848,10 +1848,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'f4bf599a8b575df685c31d9c4729a70a04e377ed',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '95e42cb72bc64eaae08344dfc994aba19ec1b59f',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'acb475026d7ac90725c629ef3664267c7aed72a1',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'ae53490d184c03116416a9c1025c40a34d37c3af',
+    Var('webrtc_git') + '/src.git' + '@' + '0bb59b4edd23d4e69de14b316312571146b6448d',
 
   # 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.
@@ -1901,7 +1901,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': '_5H_5vjRBTP8UNDdWVV93SUCTQpZLNbDO554I2hyGfQC',
+          'version': 'ZpyLtfuztdYI-RCkvm6kXlmx8ec1wYyrwXNI-lQEJKwC',
         },
       ],
       'dep_type': 'cipd',
@@ -1911,7 +1911,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': 'oufb7NWqLedvFkARhcVOwtM88ybHVCPQR0H68QULaGMC',
+          'version': 'LT6gNudC207ke0t4qv9IW_s27hoPIafnwpcfrzR8FzMC',
         },
       ],
       'dep_type': 'cipd',
@@ -1922,7 +1922,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': 'JU_7dHeAxap8o041zv0cPWrHYL4fGflKjCyWyfLJw_oC',
+          'version': 'FpwpXsMPl8Mets8uet3zfFw8nNBpl69xJoTYESLUjl8C',
         },
       ],
       'dep_type': 'cipd',
@@ -1933,7 +1933,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-arm64',
-          'version': '3ReRNh3KbBnDINX2lXkco7FighCUNJMWS3Yx8GNmrm4C',
+          'version': 'GuJOsvx5V5z8DvAM_HOVy6eBydQ0sO7dVso_p_L7KTgC',
         },
       ],
       'dep_type': 'cipd',
@@ -1985,7 +1985,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'hphB5lcrK1TCkrrCGkAzsxLMvaHd0z8EOXUjjEBRLkEC',
+        'version': 'NO3DWcSKlU_2spr4PYeaFQubbppd0FZoogZrKyMopkcC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3955,7 +3955,7 @@
     'packages' : [
       {
         'package': 'chromeos_internal/inputs/orca',
-        'version': 'ZuJr1zSFnj-_0hZCtAw2XizunFmJexkZHbXFgW-iFOUC'
+        'version': 'IGl30OhTY1hV1qeQW9S2zejZOh6Crq77wSoUGl0wVc8C'
       }
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4029,12 +4029,6 @@
       'condition': 'checkout_src_internal',
   },
 
-  'src/chrome/test/python_tests': {
-      'url': Var('chrome_git') + '/chrome/test/python_tests.git' + '@' +
-        '644bd7703b85f148564cc4038aada81f3a616d8a',
-      'condition': 'checkout_src_internal',
-  },
-
   'src/chrome/tools/memory': {
       'url': Var('chrome_git') + '/chrome/tools/memory.git' + '@' +
         '3c9359382236f6d57c91505234a2bc7fd635ba6c',
@@ -4071,7 +4065,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        '1c5fbca24a4d277baf714dca2557d254cda56c28',
+        '750f8af0ac3d188eb13a742a04c00af449b62137',
       'condition': 'checkout_src_internal',
   },
 
@@ -4131,7 +4125,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '1c35edff45df4af98046ec5a27d8e514dff98e06',
+        'c448ce76c3e84201da5db9ac4a2a579c2fabfdf3',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 53c2636..7b18baf0 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -3336,6 +3336,7 @@
             r"^remoting/host/.*",
             r"^sandbox/linux/.*",
             r"^services/webnn/tflite/graph_impl\.cc$",
+            r"^services/webnn/coreml/graph_impl\.mm$",
             r"^storage/browser/file_system/dump_file_system\.cc$",
             r"^tools/",
             r"^ui/base/resource/data_pack\.cc$",
diff --git a/android_webview/browser/safe_browsing/aw_ping_manager_factory.cc b/android_webview/browser/safe_browsing/aw_ping_manager_factory.cc
index db979f5..103e79c 100644
--- a/android_webview/browser/safe_browsing/aw_ping_manager_factory.cc
+++ b/android_webview/browser/safe_browsing/aw_ping_manager_factory.cc
@@ -51,7 +51,7 @@
       // threading the user population through for client reports
       /*get_user_population_callback=*/base::NullCallback(),
       /*get_page_load_token_callback_=*/base::NullCallback(),
-      /*hats_delegate=*/nullptr);
+      /*hats_delegate=*/nullptr, /*persister_root_path=*/context->GetPath());
 }
 
 std::string AwPingManagerFactory::GetProtocolConfigClientName() const {
diff --git a/android_webview/browser/tracing/aw_tracing_delegate.cc b/android_webview/browser/tracing/aw_tracing_delegate.cc
index 32f3747..c4af2b1 100644
--- a/android_webview/browser/tracing/aw_tracing_delegate.cc
+++ b/android_webview/browser/tracing/aw_tracing_delegate.cc
@@ -29,8 +29,13 @@
   return false;
 }
 
-AwTracingDelegate::AwTracingDelegate() {}
-AwTracingDelegate::~AwTracingDelegate() {}
+AwTracingDelegate::AwTracingDelegate()
+    : state_manager_(tracing::BackgroundTracingStateManager::CreateInstance(
+          AwBrowserProcess::GetInstance()->local_state())) {}
+AwTracingDelegate::AwTracingDelegate(
+    std::unique_ptr<tracing::BackgroundTracingStateManager> state_manager)
+    : state_manager_(std::move(state_manager)) {}
+AwTracingDelegate::~AwTracingDelegate() = default;
 
 // static
 void AwTracingDelegate::RegisterPrefs(PrefRegistrySimple* registry) {
@@ -60,18 +65,8 @@
 
 bool AwTracingDelegate::OnBackgroundTracingActive(
     bool requires_anonymized_data) {
-  // We call Initialize() only when a tracing scenario tries to start, and
-  // unless this happens we never save state. In particular, if the background
-  // tracing experiment is disabled, Initialize() will never be called, and we
-  // will thus not save state. This means that when we save the background
-  // tracing session state for one session, and then later read the state in a
-  // future session, there might have been sessions between these two where
-  // tracing was disabled. Therefore, the return value of
-  // DidLastSessionEndUnexpectedly() might not be for the directly preceding
-  // session, but instead it is the previous session where tracing was enabled.
   tracing::BackgroundTracingStateManager& state =
       tracing::BackgroundTracingStateManager::GetInstance();
-  state.Initialize(AwBrowserProcess::GetInstance()->local_state());
 
   if (!IsAllowedToStartScenario()) {
     return false;
diff --git a/android_webview/browser/tracing/aw_tracing_delegate.h b/android_webview/browser/tracing/aw_tracing_delegate.h
index f204c68..c6a0ee0c 100644
--- a/android_webview/browser/tracing/aw_tracing_delegate.h
+++ b/android_webview/browser/tracing/aw_tracing_delegate.h
@@ -10,12 +10,17 @@
 #include "content/public/browser/tracing_delegate.h"
 
 class PrefRegistrySimple;
+namespace tracing {
+class BackgroundTracingStateManager;
+}
 
 namespace android_webview {
 
 class AwTracingDelegate : public content::TracingDelegate {
  public:
   AwTracingDelegate();
+  explicit AwTracingDelegate(
+      std::unique_ptr<tracing::BackgroundTracingStateManager> state_manager);
   ~AwTracingDelegate() override;
 
   static void RegisterPrefs(PrefRegistrySimple* registry);
@@ -26,6 +31,8 @@
 
  private:
   bool IsAllowedToStartScenario() const;
+
+  std::unique_ptr<tracing::BackgroundTracingStateManager> state_manager_;
 };
 
 }  // namespace android_webview
diff --git a/android_webview/browser/tracing/aw_tracing_delegate_unittest.cc b/android_webview/browser/tracing/aw_tracing_delegate_unittest.cc
index 4631dc5..6101bac2 100644
--- a/android_webview/browser/tracing/aw_tracing_delegate_unittest.cc
+++ b/android_webview/browser/tracing/aw_tracing_delegate_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "android_webview/browser/tracing/aw_tracing_delegate.h"
 
+#include <memory>
+
 #include "android_webview/browser/aw_browser_process.h"
 #include "android_webview/browser/aw_feature_list_creator.h"
 #include "base/values.h"
@@ -31,21 +33,22 @@
         metrics::prefs::kMetricsReportingEnabled, false);
     pref_service_->SetBoolean(metrics::prefs::kMetricsReportingEnabled, true);
     tracing::RegisterPrefs(pref_service_->registry());
-    tracing::BackgroundTracingStateManager::GetInstance()
-        .SetPrefServiceForTesting(pref_service_.get());
+
+    auto state_manager = tracing::BackgroundTracingStateManager::CreateInstance(
+        pref_service_.get());
+    delegate_ = std::make_unique<android_webview::AwTracingDelegate>(
+        std::move(state_manager));
   }
 
   void TearDown() override {
     delete browser_process_;
-    tracing::BackgroundTracingStateManager::GetInstance().ResetForTesting();
   }
 
-  android_webview::AwTracingDelegate delegate_;
-
- private:
+ protected:
   content::BrowserTaskEnvironment task_environment_;
   raw_ptr<android_webview::AwBrowserProcess> browser_process_;
   std::unique_ptr<TestingPrefServiceSimple> pref_service_;
+  std::unique_ptr<android_webview::AwTracingDelegate> delegate_;
 };
 
 std::unique_ptr<content::BackgroundTracingConfig> CreateValidConfig() {
@@ -67,20 +70,20 @@
 }
 
 TEST_F(AwTracingDelegateTest, IsAllowedToBegin) {
-  EXPECT_TRUE(delegate_.OnBackgroundTracingActive(
+  EXPECT_TRUE(delegate_->OnBackgroundTracingActive(
       /*requires_anonymized_data=*/false));
-  EXPECT_TRUE(delegate_.OnBackgroundTracingIdle(
+  EXPECT_TRUE(delegate_->OnBackgroundTracingIdle(
       /*requires_anonymized_data=*/false));
 }
 
 TEST_F(AwTracingDelegateTest, IsAllowedToBeginSessionEndedUnexpectedly) {
-  tracing::BackgroundTracingStateManager::GetInstance().SaveState(
-      tracing::BackgroundTracingState::STARTED);
+  base::Value::Dict dict;
+  dict.Set("state", static_cast<int>(tracing::BackgroundTracingState::STARTED));
+  pref_service_->Set(tracing::kBackgroundTracingSessionState,
+                     base::Value(std::move(dict)));
+  tracing::BackgroundTracingStateManager::GetInstance().ResetForTesting();
 
-  base::Value dict(base::Value::Type::DICT);
-  tracing::BackgroundTracingStateManager::GetInstance().Initialize(nullptr);
-
-  EXPECT_FALSE(delegate_.OnBackgroundTracingActive(
+  EXPECT_FALSE(delegate_->OnBackgroundTracingActive(
       /*requires_anonymized_data=*/false));
 }
 
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index 82c53dc7..66054f4 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -660,6 +660,10 @@
                         + " change."),
         Flag.baseFeature("MojoIpcz"),
         Flag.baseFeature(
+                "FixDataPipeTrapBug",
+                "Used to disable a specific bug fix for a long-standing bug that may"
+                        + " have affected performance. Brief experiment for data collection"),
+        Flag.baseFeature(
                 TracingServiceFeatures.ENABLE_PERFETTO_SYSTEM_TRACING,
                 "When enabled, WebView exports trace events to the Android Perfetto service."
                         + " This works only for Android Q+."),
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 2b12a5fa..2472b66d 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1815,6 +1815,8 @@
     "system/mahi/mahi_question_answer_view.h",
     "system/mahi/mahi_ui_controller.cc",
     "system/mahi/mahi_ui_controller.h",
+    "system/mahi/mahi_ui_update.cc",
+    "system/mahi/mahi_ui_update.h",
     "system/mahi/mahi_utils.cc",
     "system/mahi/mahi_utils.h",
     "system/mahi/refresh_banner_view.cc",
@@ -4012,6 +4014,7 @@
     "system/unified/quick_settings_view_unittest.cc",
     "system/unified/quiet_mode_feature_pod_controller_unittest.cc",
     "system/unified/screen_capture_tray_item_view_unittest.cc",
+    "system/unified/unified_slider_bubble_controller_unittest.cc",
     "system/unified/unified_system_tray_unittest.cc",
     "system/unified/user_chooser_detailed_view_controller_unittest.cc",
     "system/update/update_notification_controller_unittest.cc",
@@ -4519,6 +4522,7 @@
     "//ui/chromeos/resources:resources_grit",
     "//ui/chromeos/styles:cros_tokens_color_mappings",
     "//ui/events:test_support",
+    "//ui/gfx:test_support",
     "//ui/message_center",
     "//ui/touch_selection",
     "//ui/views:test_support",
diff --git a/ash/accessibility/chromevox/touch_exploration_controller.cc b/ash/accessibility/chromevox/touch_exploration_controller.cc
index 296a157..e5d161c 100644
--- a/ash/accessibility/chromevox/touch_exploration_controller.cc
+++ b/ash/accessibility/chromevox/touch_exploration_controller.cc
@@ -408,7 +408,7 @@
   }
   if (type == ui::ET_TOUCH_MOVED)
     return DiscardEvent(continuation);
-  NOTREACHED();
+  DUMP_WILL_BE_NOTREACHED_NORETURN();
   return SendEvent(continuation, &event);
 }
 
diff --git a/ash/app_list/views/app_list_search_view_unittest.cc b/ash/app_list/views/app_list_search_view_unittest.cc
index 8d08849..6ec2f06 100644
--- a/ash/app_list/views/app_list_search_view_unittest.cc
+++ b/ash/app_list/views/app_list_search_view_unittest.cc
@@ -704,7 +704,7 @@
   check_tooltip(AppListSearchControlCategory::kHelp,
                 u"Key shortcuts, tips for using device, and more");
   check_tooltip(AppListSearchControlCategory::kImages,
-                u"Image search by content and image previews");
+                u"Search for text within images and see image previews");
   check_tooltip(AppListSearchControlCategory::kPlayStore,
                 u"Available apps from the Play Store");
   check_tooltip(AppListSearchControlCategory::kWeb,
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 71be7c4..6711911 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -2422,7 +2422,7 @@
         Key shortcuts, tips for using device, and more
       </message>
       <message name="IDS_ASH_SEARCH_CATEGORY_FILTER_MENU_IMAGES_TOOLTIP" desc="The tooltip text of the images menu item in the search category filter menu that shows what search results the category includes.">
-        Image search by content and image previews
+        Search for text within images and see image previews
       </message>
       <message name="IDS_ASH_SEARCH_CATEGORY_FILTER_MENU_PLAYSTORE_TOOLTIP" desc="The tooltip text of the playstore menu item in the search category filter menu that shows what search results the category includes.">
         Available apps from the Play Store
@@ -3295,12 +3295,27 @@
       <message name="IDS_ASH_AUDIO_SELECTION_SWITCH_OUTPUT_TITLE" desc="label used for audio selection switch output title" translateable="false">
         Switch audio output?
       </message>
+      <message name="IDS_ASH_AUDIO_SELECTION_SWITCH_SOURCE_TITLE" desc="label used for audio selection switch audio source title" translateable="false">
+        Switch audio source?
+      </message>
+      <message name="IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_TITLE" desc="label used for audio selection multiple audio sources detected title" translateable="false">
+        Multiple audio sources detected
+      </message>
       <message name="IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_OR_OUTPUT_BODY" desc="body of audio selection notification to select only or output input" translateable="false">
         Use "<ph name="DEVICE_NAME">$1<ex>Logitech Webcam</ex></ph>"
       </message>
+      <message name="IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_AND_OUTPUT_BODY" desc="body of audio selection notification to select only input and output" translateable="false">
+        Use "<ph name="DEVICE_NAME">$1<ex>Dell external display</ex></ph>" for audio input and output
+      </message>
+      <message name="IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_BODY" desc="body of audio selection notification to inform users multiple devices are detected" translateable="false">
+        Input audio is "<ph name="INPUT_DEVICE_NAME">$1<ex>Logitech Webcam</ex></ph>" and output audio is "<ph name="OUTPUT_DEVICE_NAME">$2<ex>USB Speaker</ex></ph>". Go to Settings to change.
+      </message>
       <message name="IDS_ASH_AUDIO_SELECTION_BUTTON_SWITCH" desc="label used for audio selection switch button" translateable="false">
         Switch
       </message>
+      <message name="IDS_ASH_AUDIO_SELECTION_BUTTON_SETTINGS" desc="label used for audio selection settings button" translateable="false">
+        Settings
+      </message>
       <message name="IDS_ASH_AUDIO_SELECTION_SOURCE" desc="notification source of audio selection notification" translateable="false">
         Audio selection
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_SEARCH_CATEGORY_FILTER_MENU_IMAGES_TOOLTIP.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SEARCH_CATEGORY_FILTER_MENU_IMAGES_TOOLTIP.png.sha1
index c20e91c..8ae035c6 100644
--- a/ash/ash_strings_grd/IDS_ASH_SEARCH_CATEGORY_FILTER_MENU_IMAGES_TOOLTIP.png.sha1
+++ b/ash/ash_strings_grd/IDS_ASH_SEARCH_CATEGORY_FILTER_MENU_IMAGES_TOOLTIP.png.sha1
@@ -1 +1 @@
-5bd9311bbb8608c237dceff1d0abe7d24a83db6a
\ No newline at end of file
+673beaffc51208e50ea27b1dd10e394b21ae0554
\ No newline at end of file
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 76b07ce..ecfa0b4 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1403,24 +1403,10 @@
              "GrowthCampaignsTriggerByAppOpen",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Enables new on-device recognition for legacy handwriting input.
-// This flag should be OVERRIDDEN for devices which do not have on-device
-// handwriting (b/316981973). Please check before using this flag.
-BASE_FEATURE(kHandwritingLegacyRecognition,
-             "HandwritingLegacyRecognition",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-// Enables downloading the handwriting libraries via DLC.
-// This flag should be OVERRIDDEN for devices which do not have on-device
-// handwriting (b/316981973). Please check before using this flag.
-BASE_FEATURE(kHandwritingLibraryDlc,
-             "HandwritingLibraryDlc",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // If enabled, the Help app will render the App Detail Page and entry point.
 BASE_FEATURE(kHelpAppAppDetailPage,
              "HelpAppAppDetailPage",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // If enabled, the Help app will render the Apps List page and entry point.
 BASE_FEATURE(kHelpAppAppsList,
@@ -2575,6 +2561,11 @@
              "SeaPenTextInput",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables sea pen feature for ChromeOS demo mode.
+BASE_FEATURE(kSeaPenDemoMode,
+             "SeaPenDemoMode",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Enables the system tray to show more information in larger screen.
 BASE_FEATURE(kSeamlessRefreshRateSwitching,
              "SeamlessRefreshRateSwitching",
@@ -2809,7 +2800,7 @@
 // Not used if time of day wallpaper is not enabled.
 BASE_FEATURE(kTimeOfDayWallpaperForcedAutoSchedule,
              "TimeOfDayWallpaperForcedAutoSchedule",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables retrieving time of day screen saver assets from DLC, rather than from
 // rootfs.
@@ -2907,7 +2898,7 @@
 // Controls whether the vc background replace is enabled.
 BASE_FEATURE(kVcBackgroundReplace,
              "VCBackgroundReplace",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Controls whether the DLC downloading UI for video conferencing tiles is
 // enabled.
@@ -3839,10 +3830,6 @@
   return base::FeatureList::IsEnabled(kEnableKeyboardRewriterFix);
 }
 
-bool IsLanguagePacksEnabled() {
-  return base::FeatureList::IsEnabled(kHandwritingLegacyRecognition);
-}
-
 bool IsLanguagePacksInOobeEnabled() {
   return base::FeatureList::IsEnabled(kLanguagePacksInOobe);
 }
@@ -4345,14 +4332,17 @@
   return base::FeatureList::IsEnabled(kScalableIphClientConfig);
 }
 
+bool IsSeaPenDemoModeEnabled() {
+  return IsSeaPenEnabled() && base::FeatureList::IsEnabled(kSeaPenDemoMode);
+}
+
 bool IsSeaPenEnabled() {
   return base::FeatureList::IsEnabled(kSeaPen) &&
          base::FeatureList::IsEnabled(kFeatureManagementSeaPen);
 }
 
 bool IsSeaPenTextInputEnabled() {
-  return base::FeatureList::IsEnabled(kSeaPen) &&
-         base::FeatureList::IsEnabled(kSeaPenTextInput);
+  return IsSeaPenEnabled() && base::FeatureList::IsEnabled(kSeaPenTextInput);
 }
 
 bool IsSeparateNetworkIconsEnabled() {
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 85ee6a0..4aca0bb 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -452,13 +452,10 @@
 BASE_DECLARE_FEATURE(kGrowthCampaignsShowNudgeInDefaultParent);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kGrowthCampaignsTriggerByAppOpen);
-COMPONENT_EXPORT(ASH_CONSTANTS)
-BASE_DECLARE_FEATURE(kHandwritingLegacyRecognition);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kHomeButtonWithText);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kProductivityLauncherImageSearch);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kLauncherItemColorSync);
-COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kHandwritingLibraryDlc);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kHelpAppAppDetailPage);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -781,6 +778,7 @@
 BASE_DECLARE_FEATURE(kSeamlessRefreshRateSwitching);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSeaPen);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFeatureManagementSeaPen);
+COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSeaPenDemoMode);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSeaPenTextInput);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSeparateNetworkIcons);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -1128,7 +1126,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsKeyboardBacklightToggleEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsKeyboardRewriterFixEnabled();
-COMPONENT_EXPORT(ASH_CONSTANTS) bool IsLanguagePacksEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsLanguagePacksInOobeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsLauncherContinueSectionWithRecentsEnabled();
@@ -1264,6 +1261,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsScalableIphTrackingOnlyEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsScalableIphClientConfigEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsScreenSaverDurationEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeaPenDemoModeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeaPenEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeaPenTextInputEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeparateNetworkIconsEnabled();
diff --git a/ash/constants/ash_pref_names.h b/ash/constants/ash_pref_names.h
index 893b92b..01e62ed 100644
--- a/ash/constants/ash_pref_names.h
+++ b/ash/constants/ash_pref_names.h
@@ -492,7 +492,8 @@
 // minimum speed.
 inline constexpr char kAccessibilityMouseKeysMaxSpeed[] =
     "settings.a11y.mouse_keys.max_speed";
-// A boolean pref which determines if mouse keys uses left or right hand keys.
+// An integer pref which determines if mouse keys uses left or right hand keys.
+// Values are from the ash::MouseKeysDominantHand enum.
 inline constexpr char kAccessibilityMouseKeysDominantHand[] =
     "settings.a11y.mouse_keys.dominant_hand";
 // A boolean pref which determines whether autoclick is enabled.
diff --git a/ash/display/refresh_rate_controller_unittest.cc b/ash/display/refresh_rate_controller_unittest.cc
index afcd35e1..76fbad9a 100644
--- a/ash/display/refresh_rate_controller_unittest.cc
+++ b/ash/display/refresh_rate_controller_unittest.cc
@@ -703,6 +703,100 @@
                                            ash::WindowState::Get(window.get()));
 }
 
+TEST_F(RefreshRateControllerTest, VrrUpdatesWhenBorealisWindowMoves) {
+  constexpr int kVsyncRateMinInternal = 48;
+  constexpr int kVsyncRateMinExternal = 40;
+
+  const display::Display internal = GetPrimaryDisplay();
+  const int64_t external_id = display::GetASynthesizedDisplayId();
+  std::vector<std::unique_ptr<DisplaySnapshot>> snapshots;
+  snapshots.push_back(BuildVrrPanelSnapshot(
+      internal.id(), display::DISPLAY_CONNECTION_TYPE_INTERNAL,
+      kVsyncRateMinInternal));
+  snapshots.push_back(
+      BuildVrrPanelSnapshot(external_id, display::DISPLAY_CONNECTION_TYPE_HDMI,
+                            kVsyncRateMinExternal));
+  SetUpDisplays(std::move(snapshots));
+  const display::Display external = GetSecondaryDisplay();
+  std::unique_ptr<aura::Window> window(
+      CreateTestWindowInShellWithBounds(internal.work_area()));
+  ASSERT_EQ(
+      display::Screen::GetScreen()->GetDisplayNearestWindow(window.get()).id(),
+      internal.id());
+
+  // Expect VRR to be initially disabled.
+  {
+    const DisplaySnapshot* internal_snapshot =
+        GetDisplaySnapshot(internal.id());
+    ASSERT_NE(internal_snapshot, nullptr);
+    ASSERT_TRUE(internal_snapshot->IsVrrCapable());
+    EXPECT_FALSE(internal_snapshot->IsVrrEnabled());
+    EXPECT_FALSE(GetCompositorForDisplayId(internal.id())
+                     ->max_vrr_interval_for_testing()
+                     .has_value());
+
+    const DisplaySnapshot* external_snapshot =
+        GetDisplaySnapshot(external.id());
+    ASSERT_NE(external_snapshot, nullptr);
+    ASSERT_TRUE(external_snapshot->IsVrrCapable());
+    EXPECT_FALSE(external_snapshot->IsVrrEnabled());
+    EXPECT_FALSE(GetCompositorForDisplayId(external.id())
+                     ->max_vrr_interval_for_testing()
+                     .has_value());
+  }
+
+  // Set the game mode to indicate the user is gaming on the internal display.
+  game_mode_controller_->NotifySetGameMode(GameMode::BOREALIS,
+                                           ash::WindowState::Get(window.get()));
+
+  // Expect the new state to have VRR enabled on the internal display only.
+  {
+    const DisplaySnapshot* internal_snapshot =
+        GetDisplaySnapshot(internal.id());
+    ASSERT_NE(internal_snapshot, nullptr);
+    EXPECT_TRUE(internal_snapshot->IsVrrEnabled());
+    EXPECT_EQ(base::Hertz(kVsyncRateMinInternal),
+              GetCompositorForDisplayId(internal.id())
+                  ->max_vrr_interval_for_testing());
+
+    const DisplaySnapshot* external_snapshot =
+        GetDisplaySnapshot(external.id());
+    ASSERT_NE(external_snapshot, nullptr);
+    EXPECT_FALSE(external_snapshot->IsVrrEnabled());
+    EXPECT_FALSE(GetCompositorForDisplayId(external.id())
+                     ->max_vrr_interval_for_testing()
+                     .has_value());
+  }
+
+  // Move borealis window to the external display.
+  window->SetBoundsInScreen(external.work_area(), external);
+  ASSERT_EQ(
+      display::Screen::GetScreen()->GetDisplayNearestWindow(window.get()).id(),
+      external.id());
+
+  // Expect the new state to have VRR enabled on the external display only.
+  {
+    const DisplaySnapshot* internal_snapshot =
+        GetDisplaySnapshot(internal.id());
+    ASSERT_NE(internal_snapshot, nullptr);
+    EXPECT_FALSE(internal_snapshot->IsVrrEnabled());
+    EXPECT_FALSE(GetCompositorForDisplayId(internal.id())
+                     ->max_vrr_interval_for_testing()
+                     .has_value());
+
+    const DisplaySnapshot* external_snapshot =
+        GetDisplaySnapshot(external.id());
+    ASSERT_NE(external_snapshot, nullptr);
+    EXPECT_TRUE(external_snapshot->IsVrrEnabled());
+    EXPECT_EQ(base::Hertz(kVsyncRateMinExternal),
+              GetCompositorForDisplayId(external.id())
+                  ->max_vrr_interval_for_testing());
+  }
+
+  game_mode_controller_->NotifySetGameMode(GameMode::OFF,
+                                           ash::WindowState::Get(window.get()));
+}
+
 TEST_F(RefreshRateControllerTest,
        RequestSeamlessRefreshRatesOnInternalDisplayModeChanged) {
   constexpr int64_t kDisplayId = 12345;
@@ -818,6 +912,9 @@
 }
 
 TEST_F(RefreshRateControllerTest, CompositorsGetVrrIntervalsOnSwap) {
+  constexpr int kVsyncRateMinInternal = 48;
+  constexpr int kVsyncRateMinExternal = 40;
+
   scoped_features_.Reset();
   scoped_features_.InitWithFeatures(
       /*enabled_features=*/{::features::kEnableVariableRefreshRateAlwaysOn},
@@ -827,9 +924,11 @@
   const int64_t external_id = display::GetASynthesizedDisplayId();
   std::vector<std::unique_ptr<DisplaySnapshot>> snapshots;
   snapshots.push_back(BuildVrrPanelSnapshot(
-      internal_id, display::DISPLAY_CONNECTION_TYPE_INTERNAL, 48));
-  snapshots.push_back(BuildVrrPanelSnapshot(
-      external_id, display::DISPLAY_CONNECTION_TYPE_HDMI, 40));
+      internal_id, display::DISPLAY_CONNECTION_TYPE_INTERNAL,
+      kVsyncRateMinInternal));
+  snapshots.push_back(
+      BuildVrrPanelSnapshot(external_id, display::DISPLAY_CONNECTION_TYPE_HDMI,
+                            kVsyncRateMinExternal));
   SetUpDisplays(std::move(snapshots));
 
   // Verify VRR is enabled on both displays.
@@ -853,10 +952,12 @@
   // Expect VRR intervals to be set on each display's compositor.
   {
     EXPECT_EQ(primary, GetCompositorForDisplayId(internal_id));
-    EXPECT_EQ(base::Hertz(48), primary->max_vrr_interval_for_testing());
+    EXPECT_EQ(base::Hertz(kVsyncRateMinInternal),
+              primary->max_vrr_interval_for_testing());
 
     EXPECT_EQ(secondary, GetCompositorForDisplayId(external_id));
-    EXPECT_EQ(base::Hertz(40), secondary->max_vrr_interval_for_testing());
+    EXPECT_EQ(base::Hertz(kVsyncRateMinExternal),
+              secondary->max_vrr_interval_for_testing());
   }
 
   SwapPrimaryDisplay();
@@ -865,10 +966,12 @@
   // updated accordingly.
   {
     EXPECT_EQ(primary, GetCompositorForDisplayId(external_id));
-    EXPECT_EQ(base::Hertz(40), primary->max_vrr_interval_for_testing());
+    EXPECT_EQ(base::Hertz(kVsyncRateMinExternal),
+              primary->max_vrr_interval_for_testing());
 
     EXPECT_EQ(secondary, GetCompositorForDisplayId(internal_id));
-    EXPECT_EQ(base::Hertz(48), secondary->max_vrr_interval_for_testing());
+    EXPECT_EQ(base::Hertz(kVsyncRateMinInternal),
+              secondary->max_vrr_interval_for_testing());
   }
 }
 
diff --git a/ash/game_dashboard/game_dashboard_context.cc b/ash/game_dashboard/game_dashboard_context.cc
index d2eb6c91..b49a32b 100644
--- a/ash/game_dashboard/game_dashboard_context.cc
+++ b/ash/game_dashboard/game_dashboard_context.cc
@@ -25,6 +25,7 @@
 #include "ash/public/cpp/arc_game_controls_flag.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/shell.h"
+#include "ash/strings/grit/ash_strings.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/window_state.h"
 #include "base/check.h"
@@ -33,6 +34,7 @@
 #include "chromeos/ui/frame/frame_header.h"
 #include "components/prefs/pref_service.h"
 #include "ui/aura/window.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/base/l10n/time_format.h"
 #include "ui/compositor/layer.h"
 #include "ui/events/types/event_type.h"
@@ -42,6 +44,7 @@
 #include "ui/views/animation/animation_builder.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
 #include "ui/wm/core/transient_window_manager.h"
 #include "ui/wm/core/window_util.h"
 
@@ -286,6 +289,9 @@
         game_window_, "GameDashboardToolbar", std::move(view));
     DCHECK_EQ(game_window_,
               wm::GetTransientParent(toolbar_widget_->GetNativeWindow()));
+    toolbar_widget_->widget_delegate()->SetAccessibleTitle(
+        l10n_util::GetStringUTF16(
+            IDS_ASH_GAME_DASHBOARD_TOOLBAR_TILE_BUTTON_TITLE));
     MaybeUpdateToolbarWidgetBounds();
 
     toolbar_widget_->ShowInactive();
diff --git a/ash/picker/search/picker_search_request.cc b/ash/picker/search/picker_search_request.cc
index 251c2f1..6bc3506 100644
--- a/ash/picker/search/picker_search_request.cc
+++ b/ash/picker/search/picker_search_request.cc
@@ -81,6 +81,12 @@
         query);
   }
 
+  if (!category.has_value() || category == PickerCategory::kDatesTimes) {
+    date_search_start_ = base::TimeTicks::Now();
+    // Date results is currently synchronous.
+    HandleDateSearchResults(PickerDateSearch(base::Time::Now(), query));
+  }
+
   // These searches do not have category-specific search.
   if (!category.has_value()) {
     gif_search_debouncer_.RequestSearch(
@@ -91,10 +97,6 @@
     // Emoji search is currently synchronous.
     HandleEmojiSearchResults(emoji_search_->SearchEmoji(utf8_query));
 
-    date_search_start_ = base::TimeTicks::Now();
-    // Date results is currently synchronous.
-    HandleDateSearchResults(PickerDateSearch(base::Time::Now(), query));
-
     // Math results is currently synchronous.
     HandleMathSearchResults(PickerMathSearch(query));
 
diff --git a/ash/picker/search/picker_search_request_unittest.cc b/ash/picker/search/picker_search_request_unittest.cc
index 485af307..61e71c0 100644
--- a/ash/picker/search/picker_search_request_unittest.cc
+++ b/ash/picker/search/picker_search_request_unittest.cc
@@ -907,6 +907,26 @@
   histogram.ExpectTotalCount("Ash.Picker.Search.DateProvider.QueryTime", 1);
 }
 
+TEST_F(PickerSearchRequestTest, PublishesDateResultsWhenDateCategorySelected) {
+  MockSearchResultsCallback search_results_callback;
+  EXPECT_CALL(search_results_callback, Call).Times(AnyNumber());
+  EXPECT_CALL(search_results_callback,
+              Call(PickerSearchSource::kDate, _, /*has_more_results=*/_))
+      .Times(1);
+  // Fast forward the clock to a Sunday (day_of_week = 0).
+  base::Time::Exploded exploded;
+  task_environment().GetMockClock()->Now().LocalExplode(&exploded);
+  task_environment().AdvanceClock(base::Days(7 - exploded.day_of_week));
+  task_environment().GetMockClock()->Now().LocalExplode(&exploded);
+  ASSERT_EQ(0, exploded.day_of_week);
+
+  PickerSearchRequest request(
+      u"next Friday", PickerCategory::kDatesTimes,
+      base::BindRepeating(&MockSearchResultsCallback::Call,
+                          base::Unretained(&search_results_callback)),
+      &client(), &emoji_search(), kAllCategories);
+}
+
 TEST_F(PickerSearchRequestTest, OnlyStartCrosSearchForCertainCategories) {
   EXPECT_CALL(client(),
               StartCrosSearch(Eq(u"ant"), Eq(PickerCategory::kLinks), _))
diff --git a/ash/shelf/drag_window_from_shelf_controller_unittest.cc b/ash/shelf/drag_window_from_shelf_controller_unittest.cc
index 9800f04..ea7ac031 100644
--- a/ash/shelf/drag_window_from_shelf_controller_unittest.cc
+++ b/ash/shelf/drag_window_from_shelf_controller_unittest.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "ash/shelf/drag_window_from_shelf_controller.h"
-#include "base/memory/raw_ptr.h"
 
 #include <tuple>
 
@@ -34,7 +33,9 @@
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/work_area_insets.h"
+#include "base/memory/raw_ptr.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/window_parenting_client.h"
@@ -269,6 +270,28 @@
   EXPECT_TRUE(home_screen_window->IsVisible());
 }
 
+// Test that the "No recent items" label is not visible (not created) while
+// dragging from shelf. Regression test for http://b/326091611.
+TEST_F(DragWindowFromShelfControllerTest, NoWindowsWidget) {
+  base::test::ScopedFeatureList scoped_feature_list(
+      features::kFasterSplitScreenSetup);
+
+  const gfx::Rect shelf_bounds = GetShelfBounds();
+  auto window = CreateTestWindow();
+  StartDrag(window.get(), shelf_bounds.CenterPoint());
+  Drag(gfx::Point(0, 200), 0.f, 1.f);
+
+  OverviewSession* overview_session =
+      OverviewController::Get()->overview_session();
+  ASSERT_TRUE(overview_session);
+  EXPECT_FALSE(overview_session->grid_list()[0]->no_windows_widget());
+
+  DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
+      window_drag_controller());
+  EndDrag(gfx::Point(200, 200), std::nullopt);
+  EXPECT_FALSE(overview_session->grid_list()[0]->no_windows_widget());
+}
+
 // Test the windows that were hidden before drag started may or may not reshow,
 // depending on different scenarios.
 TEST_F(DragWindowFromShelfControllerTest, MayOrMayNotReShowHiddenWindows) {
diff --git a/ash/system/camera/camera_effects_controller_unittest.cc b/ash/system/camera/camera_effects_controller_unittest.cc
index 805566b9..b504c0b 100644
--- a/ash/system/camera/camera_effects_controller_unittest.cc
+++ b/ash/system/camera/camera_effects_controller_unittest.cc
@@ -249,7 +249,7 @@
         cros::mojom::CameraEffect::kBackgroundBlur));
     EXPECT_FALSE(camera_effects_controller()->IsEffectControlAvailable(
         cros::mojom::CameraEffect::kPortraitRelight));
-    EXPECT_FALSE(camera_effects_controller()->IsEffectControlAvailable(
+    EXPECT_TRUE(camera_effects_controller()->IsEffectControlAvailable(
         cros::mojom::CameraEffect::kBackgroundReplace));
   }
 
diff --git a/ash/system/mahi/mahi_constants.h b/ash/system/mahi/mahi_constants.h
index 6a54b57..9aee80c3 100644
--- a/ash/system/mahi/mahi_constants.h
+++ b/ash/system/mahi/mahi_constants.h
@@ -13,8 +13,10 @@
 // The view ids that will be used for all children views within the Mahi panel.
 enum ViewId {
   kCloseButton = 1,
+  kContentMetadataRow,
   kContentTitle,
   kContentIcon,
+  kScrollView,
   kScrollViewContents,
   kSummaryLabel,
   kThumbsUpButton,
@@ -51,7 +53,9 @@
 inline constexpr int kPanelDefaultHeight = 492;
 inline constexpr gfx::Insets kPanelPadding(/*all=*/16);
 
-// TODO(b/319264190): Replace the string here with the correct URL.
+inline constexpr int kScrollContentsViewBottomPadding = 40;
+
+// TODO(b/333111220): Replace the string here with the correct URL.
 inline constexpr char kLearnMorePage[] = "https://google.com";
 
 inline constexpr int kRefreshBannerStackDepth = 25;
diff --git a/ash/system/mahi/mahi_error_status_view.cc b/ash/system/mahi/mahi_error_status_view.cc
index f798bc3..fd003fb 100644
--- a/ash/system/mahi/mahi_error_status_view.cc
+++ b/ash/system/mahi/mahi_error_status_view.cc
@@ -5,13 +5,12 @@
 #include "ash/system/mahi/mahi_error_status_view.h"
 
 #include <memory>
-#include <string>
-#include <variant>
 
 #include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
 #include "ash/style/typography.h"
 #include "ash/system/mahi/mahi_constants.h"
 #include "ash/system/mahi/mahi_utils.h"
+#include "base/memory/raw_ptr.h"
 #include "base/notreached.h"
 #include "chromeos/components/mahi/public/cpp/mahi_manager.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -54,10 +53,10 @@
 // ErrorContentsView -----------------------------------------------------------
 
 class ErrorContentsView : public views::FlexLayoutView,
-                          public MahiUiController::Observer {
+                          public MahiUiController::Delegate {
  public:
   explicit ErrorContentsView(MahiUiController* ui_controller)
-      : MahiUiController::Observer(ui_controller) {
+      : MahiUiController::Delegate(ui_controller) {
     // TODO(http://b/319731862): Set the image when the image resource is ready.
     views::Builder<views::FlexLayoutView>(this)
         .SetBorder(views::CreateEmptyBorder(kContentsPaddings))
@@ -91,17 +90,27 @@
   ~ErrorContentsView() override = default;
 
  private:
-  // MahiUiController::Observer:
-  void OnStateChanged(MahiUiController::State new_state,
-                      const std::optional<PayloadType>& payload) override {
-    switch (new_state) {
-      case MahiUiController::State::kError:
-        error_status_text_->SetText(
-            l10n_util::GetStringUTF16(mahi_utils::GetErrorStatusViewTextId(
-                std::get<chromeos::MahiResponseStatus>(*payload))));
+  // MahiUiController::Delegate:
+  views::View* GetView() override { return this; }
+
+  bool GetViewVisibility(VisibilityState state) const override {
+    // Always visible because its parent view controls visibility.
+    return true;
+  }
+
+  void OnUpdated(const MahiUiUpdate& update) override {
+    switch (update.type()) {
+      case MahiUiUpdateType::kErrorReceived:
+        error_status_text_->SetText(l10n_util::GetStringUTF16(
+            mahi_utils::GetErrorStatusViewTextId(update.GetError())));
         return;
-      case MahiUiController::State::kQuestionAndAnswer:
-      case MahiUiController::State::kSummaryAndOutlines:
+      case MahiUiUpdateType::kAnswerLoaded:
+      case MahiUiUpdateType::kContentsRefreshInitiated:
+      case MahiUiUpdateType::kOutlinesLoaded:
+      case MahiUiUpdateType::kQuestionPosted:
+      case MahiUiUpdateType::kRefreshAvailabilityUpdated:
+      case MahiUiUpdateType::kSummaryLoaded:
+      case MahiUiUpdateType::kSummaryAndOutlinesSectionNavigated:
         return;
     }
   }
@@ -112,7 +121,7 @@
 }  // namespace
 
 MahiErrorStatusView::MahiErrorStatusView(MahiUiController* ui_controller)
-    : MahiUiController::Observer(ui_controller) {
+    : MahiUiController::Delegate(ui_controller) {
   CHECK(chromeos::features::IsMahiEnabled());
 
   views::Builder<views::FlexLayoutView>(this)
@@ -127,17 +136,17 @@
 
 MahiErrorStatusView::~MahiErrorStatusView() = default;
 
-void MahiErrorStatusView::OnStateChanged(
-    MahiUiController::State new_state,
-    const std::optional<PayloadType>& payload) {
-  switch (new_state) {
-    case MahiUiController::State::kError:
-      SetVisible(true);
-      return;
-    case MahiUiController::State::kQuestionAndAnswer:
-    case MahiUiController::State::kSummaryAndOutlines:
-      SetVisible(false);
-      return;
+views::View* MahiErrorStatusView::GetView() {
+  return this;
+}
+
+bool MahiErrorStatusView::GetViewVisibility(VisibilityState state) const {
+  switch (state) {
+    case VisibilityState::kError:
+      return true;
+    case VisibilityState::kQuestionAndAnswer:
+    case VisibilityState::kSummaryAndOutlines:
+      return false;
   }
 }
 
diff --git a/ash/system/mahi/mahi_error_status_view.h b/ash/system/mahi/mahi_error_status_view.h
index bed7616..220e821 100644
--- a/ash/system/mahi/mahi_error_status_view.h
+++ b/ash/system/mahi/mahi_error_status_view.h
@@ -5,8 +5,6 @@
 #ifndef ASH_SYSTEM_MAHI_MAHI_ERROR_STATUS_VIEW_H_
 #define ASH_SYSTEM_MAHI_MAHI_ERROR_STATUS_VIEW_H_
 
-#include <optional>
-
 #include "ash/system/mahi/mahi_ui_controller.h"
 #include "ui/views/layout/flex_layout_view.h"
 #include "ui/views/metadata/view_factory.h"
@@ -15,15 +13,21 @@
 enum class MahiResponseStatus;
 }  // namespace chromeos
 
+namespace views {
+class View;
+}  // namespace views
+
 namespace ash {
 
+enum class VisibilityState;
+
 // Presents the current Mahi error if any. It should show when the UI controller
 // is in the error state. NOTE:
 // 1. This class is created only when the Mahi feature is enabled.
 // 2. `chromeos::MahiResponseStatus::kLowQuota` is presented in a toast view
 //    instead of this class.
 class MahiErrorStatusView : public views::FlexLayoutView,
-                            public MahiUiController::Observer {
+                            public MahiUiController::Delegate {
   METADATA_HEADER(MahiErrorStatusView, views::View)
 
  public:
@@ -33,9 +37,9 @@
   ~MahiErrorStatusView() override;
 
  private:
-  // MahiUiController::Observer:
-  void OnStateChanged(MahiUiController::State new_state,
-                      const std::optional<PayloadType>& payload) override;
+  // MahiUiController::Delegate:
+  views::View* GetView() override;
+  bool GetViewVisibility(VisibilityState state) const override;
 };
 
 BEGIN_VIEW_BUILDER(/*no export*/, MahiErrorStatusView, views::FlexLayoutView)
diff --git a/ash/system/mahi/mahi_error_status_view_pixeltest.cc b/ash/system/mahi/mahi_error_status_view_pixeltest.cc
index 5ad5ea1..170497b 100644
--- a/ash/system/mahi/mahi_error_status_view_pixeltest.cc
+++ b/ash/system/mahi/mahi_error_status_view_pixeltest.cc
@@ -70,7 +70,7 @@
           mahi_constants::ViewId::kErrorStatusView);
   ASSERT_TRUE(error_status_view);
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
-      "basics", /*revision_number=*/0, error_status_view));
+      "basics", /*revision_number=*/1, error_status_view));
 }
 
 }  // namespace ash
diff --git a/ash/system/mahi/mahi_panel_view.cc b/ash/system/mahi/mahi_panel_view.cc
index 1c61f16bf..14c265d6 100644
--- a/ash/system/mahi/mahi_panel_view.cc
+++ b/ash/system/mahi/mahi_panel_view.cc
@@ -4,10 +4,10 @@
 
 #include "ash/system/mahi/mahi_panel_view.h"
 
-#include <algorithm>
 #include <climits>
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "ash/controls/rounded_scroll_bar.h"
 #include "ash/public/cpp/new_window_delegate.h"
@@ -21,6 +21,7 @@
 #include "ash/system/mahi/mahi_error_status_view.h"
 #include "ash/system/mahi/mahi_question_answer_view.h"
 #include "ash/system/mahi/mahi_ui_controller.h"
+#include "ash/system/mahi/mahi_ui_update.h"
 #include "ash/system/mahi/summary_outlines_section.h"
 #include "base/check.h"
 #include "base/check_is_test.h"
@@ -47,6 +48,7 @@
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/text_constants.h"
 #include "ui/views/background.h"
+#include "ui/views/border.h"
 #include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/label.h"
@@ -115,6 +117,12 @@
 // NOTE: Changes to the feedback buttons' size will affect this constant.
 constexpr int kFeedbackButtonSpacing = kFeedbackButtonIconPaddingBetween - 4;
 
+// There's an 8px extra spacing between the scroll view and the input textfield
+// (on top of the default 8px spacing for the whole panel).
+constexpr int kScrollViewAndAskQuestionSpacing = 8;
+
+constexpr int kFooterSpacing = 1;
+
 // Sets focus ring for `question_textfield`. Insets need to be negative so it
 // can exceed the textfield bounds and cover the entire container.
 void InstallTextfieldFocusRing(views::View* question_textfield,
@@ -197,7 +205,7 @@
 
 // A button used to navigate to the summary & outlines section. Only shown when
 // in the Q&A view.
-class BackButton : public IconButton, public MahiUiController::Observer {
+class BackButton : public IconButton, public MahiUiController::Delegate {
   METADATA_HEADER(BackButton, IconButton)
 
  public:
@@ -220,25 +228,24 @@
             /*accessible_name=*/u"Back to summary",
             /*is_togglable=*/false,
             /*has_border=*/false),
-        MahiUiController::Observer(ui_controller) {}
+        MahiUiController::Delegate(ui_controller) {}
 
   BackButton(const BackButton&) = delete;
   BackButton& operator=(const BackButton&) = delete;
   ~BackButton() override = default;
 
  private:
-  // MahiController::Observer:
-  void OnStateChanged(MahiUiController::State new_state,
-                      const std::optional<PayloadType>& payload) override {
-    switch (new_state) {
-      case MahiUiController::State::kError:
-        break;
-      case MahiUiController::State::kQuestionAndAnswer:
-        SetVisible(true);
-        break;
-      case MahiUiController::State::kSummaryAndOutlines:
-        SetVisible(false);
-        break;
+  // MahiController::Delegate:
+  views::View* GetView() override { return this; }
+
+  bool GetViewVisibility(VisibilityState state) const override {
+    switch (state) {
+      case VisibilityState::kError:
+        return GetVisible();
+      case VisibilityState::kQuestionAndAnswer:
+        return true;
+      case VisibilityState::kSummaryAndOutlines:
+        return false;
     }
   }
 };
@@ -249,17 +256,17 @@
 BEGIN_VIEW_BUILDER(/*no export*/, BackButton, views::View)
 END_VIEW_BUILDER
 
-// ContentScrollView -----------------------------------------------------------
+// MahiScrollView -----------------------------------------------------------
 
 // Container for scrollable content in the Mahi panel, including the summary and
 // outlines section or the Q&A section. Clips its own bounds to present its
 // contents within a round-cornered container with a cutout in the bottom-right.
-class ContentScrollView : public views::ScrollView,
-                          public views::ViewTargeterDelegate {
-  METADATA_HEADER(ContentScrollView, views::ScrollView)
+class MahiScrollView : public views::ScrollView,
+                       public views::ViewTargeterDelegate {
+  METADATA_HEADER(MahiScrollView, views::ScrollView)
 
  public:
-  ContentScrollView() {
+  MahiScrollView() {
     SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
     SetBackgroundThemeColorId(cros_tokens::kCrosSysSystemOnBase);
     ClipHeightTo(/*min_height=*/0, /*max_height=*/INT_MAX);
@@ -348,7 +355,7 @@
   }
 };
 
-BEGIN_METADATA(ContentScrollView)
+BEGIN_METADATA(MahiScrollView)
 END_METADATA
 
 }  // namespace
@@ -360,7 +367,7 @@
 namespace ash {
 
 MahiPanelView::MahiPanelView(MahiUiController* ui_controller)
-    : MahiUiController::Observer(ui_controller), ui_controller_(ui_controller) {
+    : MahiUiController::Delegate(ui_controller), ui_controller_(ui_controller) {
   CHECK(ui_controller_);
 
   SetOrientation(views::LayoutOrientation::kVertical);
@@ -395,6 +402,7 @@
   // Add a source row containing the content icon and title.
   AddChildView(
       views::Builder<views::BoxLayoutView>()
+          .SetID(mahi_constants::ViewId::kContentMetadataRow)
           .SetBackground(StyleUtil::CreateThemedFullyRoundedRectBackground(
               cros_tokens::kCrosSysSystemOnBase1))
           .SetBorder(views::CreateEmptyBorder(kSourceRowPadding))
@@ -423,6 +431,9 @@
       views::Builder<views::View>()
           .SetID(mahi_constants::ViewId::kPanelContentsContainer)
           .SetUseDefaultFillLayout(true)
+          // Extra spacing between the scroll view and the input textfield.
+          .SetBorder(views::CreateEmptyBorder(
+              gfx::Insets::TLBR(0, 0, kScrollViewAndAskQuestionSpacing, 0)))
           .SetProperty(
               views::kFlexBehaviorKey,
               views::FlexSpecification(views::LayoutOrientation::kVertical,
@@ -442,10 +453,16 @@
                                views::Builder<views::View>(
                                    CreateFeedbackButton(THUMBS_DOWN))),
               views::Builder<views::ScrollView>(
-                  std::make_unique<ContentScrollView>())
+                  std::make_unique<MahiScrollView>())
+                  .SetID(mahi_constants::ViewId::kScrollView)
                   .SetContents(
                       views::Builder<views::FlexLayoutView>()
                           .SetID(mahi_constants::ViewId::kScrollViewContents)
+                          // Extra bottom padding for http://b/332766742.
+                          .SetInteriorMargin(gfx::Insets::TLBR(
+                              0, 0,
+                              mahi_constants::kScrollContentsViewBottomPadding,
+                              0))
                           .AddChildren(
                               views::Builder<SummaryOutlinesSection>(
                                   std::make_unique<SummaryOutlinesSection>(
@@ -511,20 +528,24 @@
   question_textfield_->RemoveHoverEffect();
   InstallTextfieldFocusRing(question_textfield_, send_button_);
 
-  auto footer_row = std::make_unique<views::BoxLayoutView>();
-  footer_row->SetOrientation(views::BoxLayout::Orientation::kHorizontal);
-
-  footer_row->AddChildView(
-      std::make_unique<views::Label>(GetMahiPanelDisclaimer()));
-
-  auto learn_more_link = std::make_unique<views::Link>(
-      l10n_util::GetStringUTF16(IDS_ASH_MAHI_LEARN_MORE_LINK_LABEL_TEXT));
-  learn_more_link->SetCallback(base::BindRepeating(
-      &MahiPanelView::OnLearnMoreLinkClicked, weak_ptr_factory_.GetWeakPtr()));
-  learn_more_link->SetID(mahi_constants::ViewId::kLearnMoreLink);
-  footer_row->AddChildView(std::move(learn_more_link));
-
-  AddChildView(std::move(footer_row));
+  AddChildView(
+      views::Builder<views::BoxLayoutView>()
+          .SetOrientation(views::BoxLayout::Orientation::kHorizontal)
+          .SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kCenter)
+          .SetBetweenChildSpacing(kFooterSpacing)
+          .AddChildren(
+              views::Builder<views::Label>().SetText(GetMahiPanelDisclaimer()),
+              views::Builder<views::Link>()
+                  .SetText(l10n_util::GetStringUTF16(
+                      IDS_ASH_MAHI_LEARN_MORE_LINK_LABEL_TEXT))
+                  .SetCallback(base::BindRepeating(
+                      &MahiPanelView::OnLearnMoreLinkClicked,
+                      weak_ptr_factory_.GetWeakPtr()))
+                  .SetID(mahi_constants::ViewId::kLearnMoreLink)
+                  // TODO(b/333111220): Re-enable the link when there's a
+                  // website available.
+                  .SetVisible(false))
+          .Build());
 
   // Refresh contents after all child views are built.
   ui_controller_->RefreshContents();
@@ -596,24 +617,38 @@
   return false;
 }
 
-void MahiPanelView::OnAnswerLoaded(const std::u16string& answer) {
-  // Input is re-enabled after backend has finished processing a question.
-  send_button_->SetEnabled(true);
+views::View* MahiPanelView::GetView() {
+  return this;
 }
 
-void MahiPanelView::OnContentsRefreshInitiated() {
-  auto* mahi_manager = chromeos::MahiManager::Get();
-  content_icon_->SetImage(
-      ui::ImageModel::FromImageSkia(mahi_manager->GetContentIcon()));
-  content_title_->SetText(mahi_manager->GetContentTitle());
+bool MahiPanelView::GetViewVisibility(VisibilityState state) const {
+  // Do not change visibility for `state`.
+  return GetVisible();
 }
 
-void MahiPanelView::OnStateChanged(MahiUiController::State new_state,
-                                   const std::optional<PayloadType>& payload) {
-  // Input is re-enabled after backend returns an error.
-  if (payload.has_value() &&
-      std::holds_alternative<chromeos::MahiResponseStatus>(*payload)) {
-    send_button_->SetEnabled(true);
+void MahiPanelView::OnUpdated(const MahiUiUpdate& update) {
+  switch (update.type()) {
+    case MahiUiUpdateType::kAnswerLoaded:
+      // Input is re-enabled after backend has finished processing a question.
+      send_button_->SetEnabled(true);
+      return;
+    case MahiUiUpdateType::kContentsRefreshInitiated: {
+      auto* const mahi_manager = chromeos::MahiManager::Get();
+      content_icon_->SetImage(
+          ui::ImageModel::FromImageSkia(mahi_manager->GetContentIcon()));
+      content_title_->SetText(mahi_manager->GetContentTitle());
+      return;
+    }
+    case MahiUiUpdateType::kErrorReceived:
+      // Input is re-enabled after backend returns an error.
+      send_button_->SetEnabled(true);
+      return;
+    case MahiUiUpdateType::kOutlinesLoaded:
+    case MahiUiUpdateType::kQuestionPosted:
+    case MahiUiUpdateType::kRefreshAvailabilityUpdated:
+    case MahiUiUpdateType::kSummaryLoaded:
+    case MahiUiUpdateType::kSummaryAndOutlinesSectionNavigated:
+      return;
   }
 }
 
diff --git a/ash/system/mahi/mahi_panel_view.h b/ash/system/mahi/mahi_panel_view.h
index fb1415a4..cd4d9541 100644
--- a/ash/system/mahi/mahi_panel_view.h
+++ b/ash/system/mahi/mahi_panel_view.h
@@ -26,13 +26,15 @@
 
 class IconButton;
 class MahiQuestionAnswerView;
+class MahiUiUpdate;
 class SummaryOutlinesSection;
+enum class VisibilityState;
 
 // The code for Mahi main panel view. This view is placed within
 // `MahiPanelWidget`.
 class ASH_EXPORT MahiPanelView : public views::FlexLayoutView,
                                  public views::TextfieldController,
-                                 public MahiUiController::Observer {
+                                 public MahiUiController::Delegate {
   METADATA_HEADER(MahiPanelView, views::FlexLayoutView)
 
  public:
@@ -46,11 +48,10 @@
   bool HandleKeyEvent(views::Textfield* textfield,
                       const ui::KeyEvent& key_event) override;
 
-  // MahiUiController::Observer:
-  void OnAnswerLoaded(const std::u16string& answer) override;
-  void OnContentsRefreshInitiated() override;
-  void OnStateChanged(MahiUiController::State new_state,
-                      const std::optional<PayloadType>& payload) override;
+  // MahiUiController::Delegate:
+  views::View* GetView() override;
+  bool GetViewVisibility(VisibilityState state) const override;
+  void OnUpdated(const MahiUiUpdate& update) override;
 
   // Creates the header row, which includes a back button (visible only
   // in the Q&A view), the panel title, an experiment badge and a close button.
diff --git a/ash/system/mahi/mahi_panel_view_pixeltest.cc b/ash/system/mahi/mahi_panel_view_pixeltest.cc
index c458ba9..1d053c3 100644
--- a/ash/system/mahi/mahi_panel_view_pixeltest.cc
+++ b/ash/system/mahi/mahi_panel_view_pixeltest.cc
@@ -21,8 +21,11 @@
 #include "chromeos/constants/chromeos_features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/image/image_unittest_util.h"
+#include "ui/views/controls/scroll_view.h"
 #include "ui/views/controls/textfield/textfield.h"
 #include "ui/views/test/views_test_utils.h"
+#include "ui/views/view_utils.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
@@ -62,6 +65,15 @@
     AshTestBase::TearDown();
   }
 
+  // Scroll the scroll view inside Mahi panel to the bottom.
+  void ScrollToBottom() {
+    auto* scroll_view = views::AsViewClass<views::ScrollView>(
+        panel_view()->GetViewByID(mahi_constants::ViewId::kScrollView));
+    ASSERT_TRUE(scroll_view);
+    scroll_view->vertical_scroll_bar()->ScrollByAmount(
+        views::ScrollBar::ScrollAmount::kEnd);
+  }
+
   MockMahiManager& mock_mahi_manager() { return mock_mahi_manager_; }
 
   MahiUiController* ui_controller() { return &ui_controller_; }
@@ -79,6 +91,43 @@
   std::unique_ptr<views::Widget> widget_;
 };
 
+TEST_F(MahiPanelViewPixelTest, MainPanel) {
+  ON_CALL(mock_mahi_manager(), GetContentTitle)
+      .WillByDefault(testing::Return(u"Test content title"));
+  ON_CALL(mock_mahi_manager(), GetContentIcon)
+      .WillByDefault(testing::Return(
+          gfx::test::CreateImageSkia(/*size=*/128, SK_ColorBLUE)));
+
+  ON_CALL(mock_mahi_manager(), GetSummary)
+      .WillByDefault([](chromeos::MahiManager::MahiSummaryCallback callback) {
+        std::move(callback).Run(
+            base::StrCat(std::vector<std::u16string>(30, u"Summary text ")),
+            chromeos::MahiResponseStatus::kSuccess);
+      });
+
+  ui_controller()->RefreshContents();
+  views::test::RunScheduledLayout(widget());
+
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "panel_view", /*revision_number=*/0, panel_view()));
+}
+
+TEST_F(MahiPanelViewPixelTest, ContentMetadataRow) {
+  ON_CALL(mock_mahi_manager(), GetContentTitle)
+      .WillByDefault(testing::Return(base::StrCat(
+          std::vector<std::u16string>(3, u"Long content title "))));
+  ON_CALL(mock_mahi_manager(), GetContentIcon)
+      .WillByDefault(testing::Return(
+          gfx::test::CreateImageSkia(/*size=*/200, SK_ColorRED)));
+
+  ui_controller()->RefreshContents();
+  views::test::RunScheduledLayout(widget());
+
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "content_metadata", /*revision_number=*/0,
+      panel_view()->GetViewByID(mahi_constants::ViewId::kContentMetadataRow)));
+}
+
 TEST_F(MahiPanelViewPixelTest, SummaryView) {
   ON_CALL(mock_mahi_manager(), GetSummary)
       .WillByDefault([](chromeos::MahiManager::MahiSummaryCallback callback) {
@@ -91,7 +140,7 @@
   views::test::RunScheduledLayout(widget());
 
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
-      "summary_view", /*revision_number=*/0,
+      "summary_view", /*revision_number=*/1,
       panel_view()->GetViewByID(mahi_constants::ViewId::kScrollViewContents)));
 }
 
@@ -157,8 +206,58 @@
   views::test::RunScheduledLayout(widget());
 
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
-      "question_answer_view_long_text", /*revision_number=*/2,
+      "question_answer_view_long_text", /*revision_number=*/3,
       question_answer_view));
 }
 
+TEST_F(MahiPanelViewPixelTest, SummaryViewScrollToBottom) {
+  ON_CALL(mock_mahi_manager(), GetSummary)
+      .WillByDefault([](chromeos::MahiManager::MahiSummaryCallback callback) {
+        std::move(callback).Run(
+            base::StrCat(std::vector<std::u16string>(60, u"Summary text ")),
+            chromeos::MahiResponseStatus::kSuccess);
+      });
+
+  ui_controller()->RefreshContents();
+  views::test::RunScheduledLayout(widget());
+
+  ScrollToBottom();
+
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "summary_view_bottom", /*revision_number=*/0,
+      panel_view()->GetViewByID(mahi_constants::ViewId::kScrollViewContents)));
+}
+
+TEST_F(MahiPanelViewPixelTest, QuestionAnswerViewScrollToBottom) {
+  const std::u16string answer =
+      base::StrCat(std::vector<std::u16string>(35, u"Long Answer "));
+  ON_CALL(mock_mahi_manager(), AnswerQuestion)
+      .WillByDefault(
+          [&answer](
+              const std::u16string& question, bool current_panel_content,
+              chromeos::MahiManager::MahiAnswerQuestionCallback callback) {
+            std::move(callback).Run(answer,
+                                    chromeos::MahiResponseStatus::kSuccess);
+          });
+
+  // Set a valid text in the question textfield.
+  const std::u16string question =
+      base::StrCat(std::vector<std::u16string>(25, u"Long Question "));
+  views::AsViewClass<views::Textfield>(
+      panel_view()->GetViewByID(mahi_constants::ViewId::kQuestionTextfield))
+      ->SetText(question);
+
+  // Pressing the send button should create a question and answer text bubble.
+  LeftClickOn(panel_view()->GetViewByID(
+      mahi_constants::ViewId::kAskQuestionSendButton));
+
+  views::test::RunScheduledLayout(widget());
+
+  ScrollToBottom();
+
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "question_answer_bottom", /*revision_number=*/0,
+      panel_view()->GetViewByID(mahi_constants::ViewId::kScrollViewContents)));
+}
+
 }  // namespace ash
diff --git a/ash/system/mahi/mahi_panel_view_unittest.cc b/ash/system/mahi/mahi_panel_view_unittest.cc
index 5fdb700..bfd1e7a 100644
--- a/ash/system/mahi/mahi_panel_view_unittest.cc
+++ b/ash/system/mahi/mahi_panel_view_unittest.cc
@@ -348,12 +348,18 @@
 }
 
 TEST_F(MahiPanelViewTest, LearnMoreLink) {
+  auto* learn_more_link =
+      panel_view()->GetViewByID(mahi_constants::ViewId::kLearnMoreLink);
+  // TODO(b/333111220): Remove this when the link is visible by default.
+  learn_more_link->SetVisible(true);
+  // Run layout so the link updates its size and becomes clickable.
+  views::test::RunScheduledLayout(widget());
+
   EXPECT_CALL(new_window_delegate(),
               OpenUrl(GURL(mahi_constants::kLearnMorePage),
                       NewWindowDelegate::OpenUrlFrom::kUserInteraction,
                       NewWindowDelegate::Disposition::kNewForegroundTab));
-  LeftClickOn(
-      panel_view()->GetViewByID(mahi_constants::ViewId::kLearnMoreLink));
+  LeftClickOn(learn_more_link);
   Mock::VerifyAndClearExpectations(&new_window_delegate());
 }
 
@@ -593,7 +599,8 @@
 
   EXPECT_TRUE(summary_outlines_section->GetVisible());
   EXPECT_FALSE(question_answer_view->GetVisible());
-  EXPECT_EQ(summary_outlines_section->height(),
+  EXPECT_EQ(summary_outlines_section->height() +
+                mahi_constants::kScrollContentsViewBottomPadding,
             scroll_view_contents->GetPreferredSize().height());
 
   auto* const question_textfield = views::AsViewClass<views::Textfield>(
@@ -611,7 +618,8 @@
   EXPECT_FALSE(summary_outlines_section->GetVisible());
   EXPECT_TRUE(question_answer_view->GetVisible());
 
-  EXPECT_EQ(question_answer_view->height(),
+  EXPECT_EQ(question_answer_view->height() +
+                mahi_constants::kScrollContentsViewBottomPadding,
             scroll_view_contents->GetPreferredSize().height());
 
   // Transition back to summary outlines view. Scroll view should change its
@@ -624,7 +632,8 @@
   EXPECT_TRUE(summary_outlines_section->GetVisible());
   EXPECT_FALSE(question_answer_view->GetVisible());
 
-  EXPECT_EQ(summary_outlines_section->height(),
+  EXPECT_EQ(summary_outlines_section->height() +
+                mahi_constants::kScrollContentsViewBottomPadding,
             scroll_view_contents->GetPreferredSize().height());
 }
 
@@ -1191,7 +1200,8 @@
       mahi_constants::ViewId::kQuestionAnswerErrorLabel));
 }
 
-TEST_F(MahiPanelViewTest, ClickMetrics) {
+// TODO(crbug.com/333800096): Re-enable this test
+TEST_F(MahiPanelViewTest, DISABLED_ClickMetrics) {
   base::HistogramTester histogram;
   histogram.ExpectBucketCount(mahi_constants::kMahiButtonClickHistogramName,
                               mahi_constants::PanelButton::kCloseButton, 0);
diff --git a/ash/system/mahi/mahi_question_answer_view.cc b/ash/system/mahi/mahi_question_answer_view.cc
index 5d1569d..f17bef3 100644
--- a/ash/system/mahi/mahi_question_answer_view.cc
+++ b/ash/system/mahi/mahi_question_answer_view.cc
@@ -4,7 +4,9 @@
 
 #include "ash/system/mahi/mahi_question_answer_view.h"
 
-#include <variant>
+#include <memory>
+#include <string>
+#include <utility>
 
 #include "ash/public/cpp/ash_view_ids.h"
 #include "ash/public/cpp/style/color_provider.h"
@@ -16,6 +18,7 @@
 #include "ash/style/typography.h"
 #include "ash/system/mahi/mahi_constants.h"
 #include "ash/system/mahi/mahi_ui_controller.h"
+#include "ash/system/mahi/mahi_ui_update.h"
 #include "base/check.h"
 #include "chromeos/components/mahi/public/cpp/mahi_manager.h"
 #include "components/vector_icons/vector_icons.h"
@@ -197,7 +200,7 @@
 namespace ash {
 
 MahiQuestionAnswerView::MahiQuestionAnswerView(MahiUiController* ui_controller)
-    : MahiUiController::Observer(ui_controller) {
+    : MahiUiController::Delegate(ui_controller) {
   SetOrientation(views::LayoutOrientation::kVertical);
   SetInteriorMargin(kQuestionAnswerInteriorMargin);
   SetIgnoreDefaultMainAxisMargins(true);
@@ -210,60 +213,62 @@
 
 MahiQuestionAnswerView::~MahiQuestionAnswerView() = default;
 
-void MahiQuestionAnswerView::OnAnswerLoaded(const std::u16string& answer) {
-  AddChildView(CreateQuestionAnswerRow(answer, /*is_question=*/false));
+views::View* MahiQuestionAnswerView::GetView() {
+  return this;
 }
 
-void MahiQuestionAnswerView::OnContentsRefreshInitiated() {
-  RemoveAllChildViews();
+bool MahiQuestionAnswerView::GetViewVisibility(VisibilityState state) const {
+  switch (state) {
+    case VisibilityState::kQuestionAndAnswer:
+      return true;
+    case VisibilityState::kError:
+    case VisibilityState::kSummaryAndOutlines:
+      return false;
+  }
 }
 
-void MahiQuestionAnswerView::OnStateChanged(
-    MahiUiController::State new_state,
-    const std::optional<PayloadType>& payload) {
-  switch (new_state) {
-    case MahiUiController::State::kError:
-      SetVisible(false);
+void MahiQuestionAnswerView::OnUpdated(const MahiUiUpdate& update) {
+  switch (update.type()) {
+    case MahiUiUpdateType::kAnswerLoaded:
+      AddChildView(
+          CreateQuestionAnswerRow(update.GetAnswer(), /*is_question=*/false));
       return;
-    case MahiUiController::State::kQuestionAndAnswer:
-      SetVisible(true);
-      if (std::holds_alternative<std::u16string>(*payload)) {
-        AddChildView(CreateQuestionAnswerRow(std::get<std::u16string>(*payload),
-                                             /*is_question=*/true));
+    case MahiUiUpdateType::kContentsRefreshInitiated:
+      RemoveAllChildViews();
+      return;
+    case MahiUiUpdateType::kErrorReceived:
+      // Creates `error_bubble_` if having an inappropriate question error.
+      if (update.GetError() == chromeos::MahiResponseStatus::kInappropriate) {
+        if (error_bubble_) {
+          LOG(ERROR) << "Tried to add a new error bubble when there is an "
+                        "existing one.";
+          return;
+        }
+        // Building `ErrorBubble` is synchronous. Therefore, it is safe to pass
+        // the reference to `error_bubble` to the callback.
+        AddChildView(
+            views::Builder<ErrorBubble>()
+                .AfterBuild(base::BindOnce(
+                    [](views::ViewTracker& error_bubble, ErrorBubble* self) {
+                      error_bubble.SetView(self);
+                    },
+                    std::ref(error_bubble_)))
+                .Build());
       }
-      MaybeUpdateErrorBubble(*payload);
       return;
-    case MahiUiController::State::kSummaryAndOutlines:
-      SetVisible(false);
+    case MahiUiUpdateType::kQuestionPosted:
+      AddChildView(CreateQuestionAnswerRow(update.GetQuestion(),
+                                           /*is_question=*/true));
+      // Destroys `error_bubble_` if any when the user posts a new question.
+      if (error_bubble_) {
+        RemoveChildViewT(error_bubble_.view());
+      }
       return;
-  }
-}
-
-void MahiQuestionAnswerView::MaybeUpdateErrorBubble(
-    const PayloadType& payload) {
-  if (std::holds_alternative<std::u16string>(payload) && error_bubble_) {
-    RemoveChildViewT(error_bubble_.view());
-    return;
-  }
-
-  if (std::holds_alternative<chromeos::MahiResponseStatus>(payload)) {
-    CHECK_EQ(std::get<chromeos::MahiResponseStatus>(payload),
-             chromeos::MahiResponseStatus::kInappropriate);
-
-    if (error_bubble_) {
-      LOG(ERROR)
-          << "Tried to add a new error bubble when there is an existing one.";
+    case MahiUiUpdateType::kOutlinesLoaded:
+    case MahiUiUpdateType::kRefreshAvailabilityUpdated:
+    case MahiUiUpdateType::kSummaryLoaded:
+    case MahiUiUpdateType::kSummaryAndOutlinesSectionNavigated:
       return;
-    }
-
-    // Building `ErrorBubble` is synchronous. Therefore, it is safe to pass the
-    // reference to `error_bubble` to the callback.
-    AddChildView(views::Builder<ErrorBubble>()
-                     .AfterBuild(base::BindOnce(
-                         [](views::ViewTracker& error_bubble,
-                            ErrorBubble* self) { error_bubble.SetView(self); },
-                         std::ref(error_bubble_)))
-                     .Build());
   }
 }
 
diff --git a/ash/system/mahi/mahi_question_answer_view.h b/ash/system/mahi/mahi_question_answer_view.h
index ed97873..2c023377d 100644
--- a/ash/system/mahi/mahi_question_answer_view.h
+++ b/ash/system/mahi/mahi_question_answer_view.h
@@ -5,12 +5,8 @@
 #ifndef ASH_SYSTEM_MAHI_MAHI_QUESTION_ANSWER_VIEW_H_
 #define ASH_SYSTEM_MAHI_MAHI_QUESTION_ANSWER_VIEW_H_
 
-#include <optional>
-#include <string>
-
 #include "ash/ash_export.h"
 #include "ash/system/mahi/mahi_ui_controller.h"
-#include "base/memory/raw_ptr.h"
 #include "chromeos/components/mahi/public/cpp/mahi_manager.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/layout/flex_layout_view.h"
@@ -23,9 +19,12 @@
 
 namespace ash {
 
+class MahiUiUpdate;
+enum class VisibilityState;
+
 // Mahi Q&A View.
 class ASH_EXPORT MahiQuestionAnswerView : public views::FlexLayoutView,
-                                          public MahiUiController::Observer {
+                                          public MahiUiController::Delegate {
   METADATA_HEADER(MahiQuestionAnswerView, views::FlexLayoutView)
 
  public:
@@ -35,16 +34,10 @@
   ~MahiQuestionAnswerView() override;
 
  private:
-  // MahiUiController::Observer:
-  void OnAnswerLoaded(const std::u16string& answer) override;
-  void OnContentsRefreshInitiated() override;
-  void OnStateChanged(MahiUiController::State new_state,
-                      const std::optional<PayloadType>& payload) override;
-
-  // Creates `error_bubble_` if `payload` suggests an error introduced by the
-  // most recent question; destroys `error_bubble_` if any when `payload`
-  // suggests a new question from the user.
-  void MaybeUpdateErrorBubble(const PayloadType& payload);
+  // MahiUiController::Delegate:
+  views::View* GetView() override;
+  bool GetViewVisibility(VisibilityState state) const override;
+  void OnUpdated(const MahiUiUpdate& update) override;
 
   // Tracks the bubble that presents the error introduced by the most recent
   // question. The bubble is created when the error occurs and is destroyed when
diff --git a/ash/system/mahi/mahi_ui_controller.cc b/ash/system/mahi/mahi_ui_controller.cc
index 52014232f..540f295e 100644
--- a/ash/system/mahi/mahi_ui_controller.cc
+++ b/ash/system/mahi/mahi_ui_controller.cc
@@ -6,6 +6,7 @@
 
 #include "base/logging.h"
 #include "chromeos/components/mahi/public/cpp/mahi_manager.h"
+#include "ui/views/view.h"
 
 namespace ash {
 
@@ -18,14 +19,14 @@
 
 }  // namespace
 
-// MahiUiController::Observer --------------------------------------------------
+// MahiUiController::delegate --------------------------------------------------
 
-MahiUiController::Observer::Observer(MahiUiController* ui_controller) {
+MahiUiController::Delegate::Delegate(MahiUiController* ui_controller) {
   CHECK(ui_controller);
   observation_.Observe(ui_controller);
 }
 
-MahiUiController::Observer::~Observer() = default;
+MahiUiController::Delegate::~Delegate() = default;
 
 // MahiUiController ------------------------------------------------------------
 
@@ -33,36 +34,37 @@
 
 MahiUiController::~MahiUiController() = default;
 
-void MahiUiController::AddObserver(Observer* observer) {
-  observers_.AddObserver(observer);
+void MahiUiController::AddDelegate(Delegate* delegate) {
+  delegates_.AddObserver(delegate);
 }
 
-void MahiUiController::RemoveObserver(Observer* observer) {
-  observers_.RemoveObserver(observer);
+void MahiUiController::RemoveDelegate(Delegate* delegate) {
+  delegates_.RemoveObserver(delegate);
 }
 
 void MahiUiController::NavigateToSummaryOutlinesSection() {
-  SetStateAndNotify(State::kSummaryAndOutlines,
-                    /*payload=*/std::nullopt);
+  SetVisibilityStateAndNotifyUiUpdate(
+      VisibilityState::kSummaryAndOutlines,
+      MahiUiUpdate(MahiUiUpdateType::kSummaryAndOutlinesSectionNavigated));
 }
 
 void MahiUiController::NotifyRefreshAvailabilityChanged(bool available) {
-  for (auto& observer : observers_) {
-    observer.OnRefreshAvailabilityChanged(available);
-  }
+  NotifyUiUpdate(
+      MahiUiUpdate(MahiUiUpdateType::kRefreshAvailabilityUpdated, available));
 }
 
 void MahiUiController::RefreshContents() {
   NavigateToSummaryOutlinesSection();
-
-  for (auto& observer : observers_) {
-    observer.OnContentsRefreshInitiated();
-  }
+  NotifyUiUpdate(MahiUiUpdate(MahiUiUpdateType::kContentsRefreshInitiated));
 }
 
 void MahiUiController::SendQuestion(const std::u16string& question,
                                     bool current_panel_content) {
-  SetStateAndNotify(State::kQuestionAndAnswer, question);
+  // Display the Q&A section.
+  SetVisibilityStateAndNotifyUiUpdate(
+      VisibilityState::kQuestionAndAnswer,
+      MahiUiUpdate(MahiUiUpdateType::kQuestionPosted, question));
+
   chromeos::MahiManager::Get()->AnswerQuestion(
       question, current_panel_content,
       base::BindOnce(&MahiUiController::OnAnswerLoaded,
@@ -87,22 +89,36 @@
   // The presentation of the inappropriate error during
   // `State::kQuestionAndAnswer` should be embedded into the Q&A view instead
   // of a separate view.
+  const MahiUiUpdate update(MahiUiUpdateType::kErrorReceived, status);
   if (status == chromeos::MahiResponseStatus::kInappropriate &&
-      state_ == State::kQuestionAndAnswer) {
-    SetStateAndNotify(State::kQuestionAndAnswer, status);
+      visibility_state_ == VisibilityState::kQuestionAndAnswer) {
+    NotifyUiUpdate(update);
     return;
   }
 
-  SetStateAndNotify(State::kError, status);
+  // Display the view that presents the error.
+  SetVisibilityStateAndNotifyUiUpdate(VisibilityState::kError, update);
 }
 
-void MahiUiController::SetStateAndNotify(
-    State new_state,
-    const std::optional<Observer::PayloadType>& payload) {
-  state_ = new_state;
+void MahiUiController::NotifyUiUpdate(const MahiUiUpdate& update) {
+  for (auto& delegate : delegates_) {
+    delegate.OnUpdated(update);
+  }
+}
 
-  for (auto& observer : observers_) {
-    observer.OnStateChanged(state_, payload);
+void MahiUiController::SetVisibilityStateAndNotifyUiUpdate(
+    VisibilityState state,
+    const MahiUiUpdate& update) {
+  visibility_state_ = state;
+
+  for (auto& delegate : delegates_) {
+    views::View* const associated_view = delegate.GetView();
+    if (const bool target_visible = delegate.GetViewVisibility(state);
+        target_visible != associated_view->GetVisible()) {
+      associated_view->SetVisible(target_visible);
+    }
+
+    delegate.OnUpdated(update);
   }
 }
 
@@ -119,9 +135,9 @@
     LOG(ERROR) << "Received an empty Mahi answer";
   }
 
-  for (auto& observer : observers_) {
-    observer.OnAnswerLoaded(answer.value_or(std::u16string()));
-  }
+  const std::u16string answer_after_process = answer.value_or(std::u16string());
+  NotifyUiUpdate(
+      MahiUiUpdate(MahiUiUpdateType::kAnswerLoaded, answer_after_process));
 }
 
 void MahiUiController::OnOutlinesLoaded(
@@ -132,9 +148,7 @@
     return;
   }
 
-  for (auto& observer : observers_) {
-    observer.OnOutlinesLoaded(outlines);
-  }
+  NotifyUiUpdate(MahiUiUpdate(MahiUiUpdateType::kOutlinesLoaded, outlines));
 }
 
 void MahiUiController::OnSummaryLoaded(std::u16string summary_text,
@@ -144,9 +158,7 @@
     return;
   }
 
-  for (auto& observer : observers_) {
-    observer.OnSummaryLoaded(summary_text);
-  }
+  NotifyUiUpdate(MahiUiUpdate(MahiUiUpdateType::kSummaryLoaded, summary_text));
 }
 
 }  // namespace ash
diff --git a/ash/system/mahi/mahi_ui_controller.h b/ash/system/mahi/mahi_ui_controller.h
index 22dc099..10b30c4 100644
--- a/ash/system/mahi/mahi_ui_controller.h
+++ b/ash/system/mahi/mahi_ui_controller.h
@@ -7,69 +7,44 @@
 
 #include <optional>
 #include <string>
-#include <variant>
 #include <vector>
 
 #include "ash/ash_export.h"
+#include "ash/system/mahi/mahi_ui_update.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/scoped_observation_traits.h"
 #include "chromeos/components/mahi/public/cpp/mahi_manager.h"
 
+namespace views {
+class View;
+}  // namespace views
+
 namespace ash {
 
-// Communicates with `chromeos::MahiManager` and notifies observers of updates.
+// Communicates with `chromeos::MahiManager` and notifies delegates of updates.
 class ASH_EXPORT MahiUiController {
  public:
-  enum class State {
-    // The state that presents the error triggered by user actions.
-    kError,
-
-    // The state that presents questions and answers.
-    kQuestionAndAnswer,
-
-    // The state that presents the summary and outlines.
-    kSummaryAndOutlines,
-  };
-
-  class Observer : public base::CheckedObserver {
+  // Establishes the connection between `MahiUiController` and dependent views.
+  class Delegate : public base::CheckedObserver {
    public:
-    explicit Observer(MahiUiController* ui_controller);
-    Observer(const Observer&) = delete;
-    Observer& operator=(const Observer&) = delete;
-    ~Observer() override;
+    explicit Delegate(MahiUiController* ui_controller);
+    Delegate(const Delegate&) = delete;
+    Delegate& operator=(const Delegate&) = delete;
+    ~Delegate() override;
 
-    // Called when an answer is loaded with a success.
-    virtual void OnAnswerLoaded(const std::u16string& answer) {}
+    // Returns the view that associates with the delegate.
+    virtual views::View* GetView() = 0;
 
-    // Called when a request to refresh the panel contents was initiated.
-    virtual void OnContentsRefreshInitiated() {}
+    // Returns the visibility of the delegate's associated view for `state`.
+    virtual bool GetViewVisibility(VisibilityState state) const = 0;
 
-    // Called when outlines are loaded with a success.
-    virtual void OnOutlinesLoaded(
-        const std::vector<chromeos::MahiOutline>& outlines) {}
-
-    // Called when content refresh availability changes.
-    virtual void OnRefreshAvailabilityChanged(bool available) {}
-
-    // Called when the current state of `MahiUiController` updates. `payload`
-    // provides more details on state transition:
-    // 1. If `new_state` is `State::kError`, `payload` is the error that leads
-    //    to this update.
-    // 2. if `new_state` is `kQuestionAndAnswer`, `payload` is either (1) a
-    //    question string or (2) an error introduced by the previous question.
-    using PayloadType = std::variant</*posted_question=*/std::u16string,
-                                     /*error=*/chromeos::MahiResponseStatus>;
-    virtual void OnStateChanged(State new_state,
-                                const std::optional<PayloadType>& payload) {}
-
-    // Called when a summary is loaded with a success.
-    virtual void OnSummaryLoaded(const std::u16string& summary) {}
+    // Notifies of a Mahi UI update.
+    virtual void OnUpdated(const MahiUiUpdate& update) {}
 
    private:
-    base::ScopedObservation<MahiUiController, MahiUiController::Observer>
-        observation_{this};
+    base::ScopedObservation<MahiUiController, Delegate> observation_{this};
   };
 
   MahiUiController();
@@ -77,13 +52,13 @@
   MahiUiController& operator=(const MahiUiController&) = delete;
   ~MahiUiController();
 
-  void AddObserver(Observer* observer);
-  void RemoveObserver(Observer* observer);
+  void AddDelegate(Delegate* delegate);
+  void RemoveDelegate(Delegate* delegate);
 
-  // Navigates to the summary & outlines section and notifies observers.
+  // Navigates to the summary & outlines section and notifies delegates.
   void NavigateToSummaryOutlinesSection();
 
-  // Notifies UI observers that there is a content refresh availability change.
+  // Notifies delegates that there is a content refresh availability change.
   void NotifyRefreshAvailabilityChanged(bool available);
 
   // Updates the content icon and title, calls `UpdateSummaryAndOutlines` and
@@ -95,7 +70,7 @@
   void SendQuestion(const std::u16string& question, bool current_panel_content);
 
   // Sends requests to the backend to update summary and outlines.
-  // `observers_` will be notified of the updated summary and outlines when
+  // `delegates_` will be notified of the updated summary and outlines when
   // requests are fulfilled.
   void UpdateSummaryAndOutlines();
 
@@ -104,10 +79,12 @@
   // `chromeos::MahiResponseStatus::kSuccess`.
   void HandleErrorStatus(chromeos::MahiResponseStatus status);
 
-  // Updates `state_` and notifies observers. `payload` provides more details on
-  // state transition.
-  void SetStateAndNotify(State new_state,
-                         const std::optional<Observer::PayloadType>& payload);
+  // Notifies `delegates_` of `update`.
+  void NotifyUiUpdate(const MahiUiUpdate& update);
+
+  // Sets the visibility state and notifies `delegates_` of `update`.
+  void SetVisibilityStateAndNotifyUiUpdate(VisibilityState state,
+                                           const MahiUiUpdate& update);
 
   // Callbacks of `chromeos::MahiManager` APIs ---------------------------------
 
@@ -120,14 +97,31 @@
   void OnSummaryLoaded(std::u16string summary_text,
                        chromeos::MahiResponseStatus status);
 
-  // The current state. Be `State::kSummaryAndOutlines` by default.
-  State state_ = State::kSummaryAndOutlines;
+  // The current state. Use `VisibilityState::kSummaryAndOutlines` by default.
+  VisibilityState visibility_state_ = VisibilityState::kSummaryAndOutlines;
 
-  base::ObserverList<Observer> observers_;
+  base::ObserverList<Delegate> delegates_;
 
   base::WeakPtrFactory<MahiUiController> weak_ptr_factory_{this};
 };
 
 }  // namespace ash
 
+namespace base {
+
+template <>
+struct ScopedObservationTraits<ash::MahiUiController,
+                               ash::MahiUiController::Delegate> {
+  static void AddObserver(ash::MahiUiController* controller,
+                          ash::MahiUiController::Delegate* delegate) {
+    controller->AddDelegate(delegate);
+  }
+  static void RemoveObserver(ash::MahiUiController* controller,
+                             ash::MahiUiController::Delegate* delegate) {
+    controller->RemoveDelegate(delegate);
+  }
+};
+
+}  // namespace base
+
 #endif  // ASH_SYSTEM_MAHI_MAHI_UI_CONTROLLER_H_
diff --git a/ash/system/mahi/mahi_ui_update.cc b/ash/system/mahi/mahi_ui_update.cc
new file mode 100644
index 0000000..16bf299
--- /dev/null
+++ b/ash/system/mahi/mahi_ui_update.cc
@@ -0,0 +1,120 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/mahi/mahi_ui_update.h"
+
+#include "base/check_op.h"
+#include "chromeos/components/mahi/public/cpp/mahi_manager.h"
+
+namespace ash {
+
+// MahiUiUpdate ----------------------------------------------------------------
+
+MahiUiUpdate::MahiUiUpdate(MahiUiUpdateType type)
+    : type_(type), payload_(std::nullopt) {
+  CheckTypeMatchesPayload();
+}
+
+MahiUiUpdate::MahiUiUpdate(MahiUiUpdateType type,
+                           chromeos::MahiResponseStatus payload)
+    : type_(type), payload_(payload) {
+  CheckTypeMatchesPayload();
+}
+
+MahiUiUpdate::MahiUiUpdate(MahiUiUpdateType type, bool payload)
+    : type_(type), payload_(payload) {
+  CheckTypeMatchesPayload();
+}
+
+MahiUiUpdate::MahiUiUpdate(MahiUiUpdateType type, const std::u16string& payload)
+    : type_(type), payload_(payload) {
+  CheckTypeMatchesPayload();
+}
+
+MahiUiUpdate::MahiUiUpdate(MahiUiUpdateType type,
+                           const std::vector<chromeos::MahiOutline>& payload)
+    : type_(type), payload_(payload) {
+  CheckTypeMatchesPayload();
+}
+
+MahiUiUpdate::~MahiUiUpdate() = default;
+
+const std::u16string& MahiUiUpdate::GetAnswer() const {
+  CHECK_EQ(type_, MahiUiUpdateType::kAnswerLoaded);
+  return std::get<std::reference_wrapper<const std::u16string>>(*payload_);
+}
+
+chromeos::MahiResponseStatus MahiUiUpdate::GetError() const {
+  CHECK_EQ(type_, MahiUiUpdateType::kErrorReceived);
+  return std::get<chromeos::MahiResponseStatus>(*payload_);
+}
+
+const std::vector<chromeos::MahiOutline>& MahiUiUpdate::GetOutlines() const {
+  CHECK_EQ(type_, MahiUiUpdateType::kOutlinesLoaded);
+  return std::get<
+      std::reference_wrapper<const std::vector<chromeos::MahiOutline>>>(
+      *payload_);
+}
+
+const std::u16string& MahiUiUpdate::GetQuestion() const {
+  CHECK_EQ(type_, MahiUiUpdateType::kQuestionPosted);
+  return std::get<std::reference_wrapper<const std::u16string>>(*payload_);
+}
+
+bool MahiUiUpdate::GetRefreshAvailability() const {
+  CHECK_EQ(type_, MahiUiUpdateType::kRefreshAvailabilityUpdated);
+  return std::get<bool>(*payload_);
+}
+
+const std::u16string& MahiUiUpdate::GetSummary() const {
+  CHECK_EQ(type_, MahiUiUpdateType::kSummaryLoaded);
+  return std::get<std::reference_wrapper<const std::u16string>>(*payload_);
+}
+
+void MahiUiUpdate::CheckTypeMatchesPayload() {
+  switch (type_) {
+    case MahiUiUpdateType::kAnswerLoaded:
+      CHECK(payload_.has_value());
+      CHECK(
+          std::holds_alternative<std::reference_wrapper<const std::u16string>>(
+              *payload_));
+      break;
+    case MahiUiUpdateType::kContentsRefreshInitiated:
+      CHECK(!payload_.has_value());
+      break;
+    case MahiUiUpdateType::kErrorReceived:
+      CHECK(payload_.has_value());
+      CHECK(std::holds_alternative<chromeos::MahiResponseStatus>(*payload_));
+      CHECK_NE(std::get<chromeos::MahiResponseStatus>(*payload_),
+               chromeos::MahiResponseStatus::kSuccess);
+      break;
+    case MahiUiUpdateType::kOutlinesLoaded:
+      CHECK(payload_.has_value());
+      CHECK(std::holds_alternative<
+            std::reference_wrapper<const std::vector<chromeos::MahiOutline>>>(
+          *payload_));
+      break;
+    case MahiUiUpdateType::kQuestionPosted:
+      CHECK(payload_.has_value());
+      CHECK(
+          std::holds_alternative<std::reference_wrapper<const std::u16string>>(
+              *payload_));
+      break;
+    case MahiUiUpdateType::kRefreshAvailabilityUpdated:
+      CHECK(payload_.has_value());
+      CHECK(std::holds_alternative<bool>(*payload_));
+      break;
+    case MahiUiUpdateType::kSummaryAndOutlinesSectionNavigated:
+      CHECK(!payload_.has_value());
+      break;
+    case MahiUiUpdateType::kSummaryLoaded:
+      CHECK(payload_.has_value());
+      CHECK(
+          std::holds_alternative<std::reference_wrapper<const std::u16string>>(
+              *payload_));
+      break;
+  }
+}
+
+}  // namespace ash
diff --git a/ash/system/mahi/mahi_ui_update.h b/ash/system/mahi/mahi_ui_update.h
new file mode 100644
index 0000000..56879498
--- /dev/null
+++ b/ash/system/mahi/mahi_ui_update.h
@@ -0,0 +1,128 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_SYSTEM_MAHI_MAHI_UI_UPDATE_H_
+#define ASH_SYSTEM_MAHI_MAHI_UI_UPDATE_H_
+
+#include <functional>
+#include <optional>
+#include <variant>
+#include <vector>
+
+namespace chromeos {
+struct MahiOutline;
+enum class MahiResponseStatus;
+}  // namespace chromeos
+
+namespace ash {
+
+enum class VisibilityState {
+  // The state that shows the view displaying errors from user actions.
+  // NOTE: Not all errors should be displayed in this state.
+  kError,
+
+  // The state that shows the view displaying questions and answers.
+  kQuestionAndAnswer,
+
+  // The state that shows the view displaying summary and outlines.
+  kSummaryAndOutlines,
+};
+
+enum class MahiUiUpdateType {
+  // An answer is loaded successfully.
+  kAnswerLoaded,
+
+  // A request to refresh the panel contents is initiated.
+  kContentsRefreshInitiated,
+
+  // An error is received.
+  kErrorReceived,
+
+  // Outlines are loaded successfully.
+  kOutlinesLoaded,
+
+  // A question is posted by user.
+  kQuestionPosted,
+
+  // The content refresh availability changes.
+  kRefreshAvailabilityUpdated,
+
+  // The summary and outlines section is requested to show.
+  kSummaryAndOutlinesSectionNavigated,
+
+  // A summary is loaded with a success.
+  kSummaryLoaded,
+};
+
+// Indicates a change that triggers a visible update on the Mahi UI.
+class MahiUiUpdate {
+ public:
+  explicit MahiUiUpdate(MahiUiUpdateType type);
+  MahiUiUpdate(MahiUiUpdateType type, chromeos::MahiResponseStatus payload);
+  MahiUiUpdate(MahiUiUpdateType type, bool payload);
+
+  // NOTE: `MahiUiUpdate` caches the const reference to `payload`, not a copy.
+  // The class user has the duty to ensure the original `payload` object
+  // outlives the `MahiUiUpdate` instance.
+  MahiUiUpdate(MahiUiUpdateType type, const std::u16string& payload);
+  MahiUiUpdate(MahiUiUpdateType type,
+               const std::vector<chromeos::MahiOutline>& payload);
+
+  MahiUiUpdate(const MahiUiUpdate&) = delete;
+  MahiUiUpdate& operator=(const MahiUiUpdate&) = delete;
+  ~MahiUiUpdate();
+
+  // Returns the answer from `payload`.
+  // NOTE: This function should be called only if `type` is `kAnswerLoaded`.
+  const std::u16string& GetAnswer() const;
+
+  // Returns the error from `payload`.
+  // NOTE: This function should be called only if `type` is `kErrorReceived`.
+  chromeos::MahiResponseStatus GetError() const;
+
+  // Returns the outlines from `payload`.
+  // NOTE: This function should be called only if `type` is `kOutlinesLoaded`.
+  const std::vector<chromeos::MahiOutline>& GetOutlines() const;
+
+  // Returns the question from `payload`.
+  // NOTE: This function should be called only if `type` is `kQuestionPosted`.
+  const std::u16string& GetQuestion() const;
+
+  // Returns the refresh availability from `payload`.
+  // NOTE: This function should be called only if `type` is
+  // `kRefreshAvailabilityUpdated`.
+  bool GetRefreshAvailability() const;
+
+  // Returns the summary from `payload`.
+  // NOTE: This function should be called only if `type` is `kSummaryLoaded`.
+  const std::u16string& GetSummary() const;
+
+  MahiUiUpdateType type() const { return type_; }
+
+ private:
+  // Check that `type_` matches the actual type of `payload_`.
+  void CheckTypeMatchesPayload();
+
+  const MahiUiUpdateType type_;
+
+  // Provides more details on the update. Its actual data depends on `type`:
+  // For `kAnswerLoaded`, `payload` is an answer;
+  // For `kContentsRefreshInitiated`, `payload` is `std::nullopt`;
+  // For `kErrorReceived`, `payload` is an error;
+  // For `kOutlinesLoaded`, `payload` is an array of outlines;
+  // For `kQuestionPosted`, `payload` is a question;
+  // For `kRefreshAvailabilityUpdated`, `payload` is a boolean;
+  // For `kSummaryAndOutlinesSectionNavigated`, `payload` is `std::nullopt`;
+  // For `kSummaryLoaded`, `payload` is a summary.
+  using PayloadType = std::variant<
+      std::reference_wrapper<const std::u16string>,
+      chromeos::MahiResponseStatus,
+      std::reference_wrapper<const std::vector<chromeos::MahiOutline>>,
+      bool>;
+  const std::optional<PayloadType> payload_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_MAHI_MAHI_UI_UPDATE_H_
diff --git a/ash/system/mahi/refresh_banner_view.cc b/ash/system/mahi/refresh_banner_view.cc
index ffa0c68..6d8824f8 100644
--- a/ash/system/mahi/refresh_banner_view.cc
+++ b/ash/system/mahi/refresh_banner_view.cc
@@ -84,7 +84,7 @@
 }  // namespace
 
 RefreshBannerView::RefreshBannerView(MahiUiController* ui_controller)
-    : MahiUiController::Observer(ui_controller), ui_controller_(ui_controller) {
+    : MahiUiController::Delegate(ui_controller), ui_controller_(ui_controller) {
   CHECK(ui_controller_);
 
   SetOrientation(views::LayoutOrientation::kHorizontal);
@@ -173,21 +173,19 @@
 }
 
 void RefreshBannerView::Hide() {
-  if (!GetVisible()) {
-    return;
+  if (GetVisible()) {
+    views::AnimationBuilder()
+        .OnEnded(base::BindOnce(
+            [](const base::WeakPtr<views::View>& view) {
+              if (view) {
+                view->SetVisible(false);
+              }
+            },
+            weak_ptr_factory_.GetWeakPtr()))
+        .Once()
+        .SetDuration(kRefreshBannerAnimationDurationMs)
+        .SetOpacity(this, 0.0);
   }
-
-  views::AnimationBuilder()
-      .OnEnded(base::BindOnce(
-          [](base::WeakPtr<views::View> view) {
-            if (view) {
-              view->SetVisible(false);
-            }
-          },
-          weak_ptr_factory_.GetWeakPtr()))
-      .Once()
-      .SetDuration(kRefreshBannerAnimationDurationMs)
-      .SetOpacity(this, 0.0);
 }
 
 void RefreshBannerView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
@@ -199,15 +197,35 @@
   }
 }
 
-void RefreshBannerView::OnContentsRefreshInitiated() {
-  Hide();
+views::View* RefreshBannerView::GetView() {
+  return this;
 }
 
-void RefreshBannerView::OnRefreshAvailabilityChanged(bool available) {
-  if (available) {
-    Show();
-  } else {
-    Hide();
+bool RefreshBannerView::GetViewVisibility(VisibilityState state) const {
+  // Do not change visibility because visibility depends on the refresh
+  // availability instead of `state`.
+  return GetVisible();
+}
+
+void RefreshBannerView::OnUpdated(const MahiUiUpdate& update) {
+  switch (update.type()) {
+    case MahiUiUpdateType::kRefreshAvailabilityUpdated:
+      if (update.GetRefreshAvailability()) {
+        Show();
+      } else {
+        Hide();
+      }
+      return;
+    case MahiUiUpdateType::kContentsRefreshInitiated:
+      Hide();
+      return;
+    case MahiUiUpdateType::kErrorReceived:
+    case MahiUiUpdateType::kAnswerLoaded:
+    case MahiUiUpdateType::kOutlinesLoaded:
+    case MahiUiUpdateType::kQuestionPosted:
+    case MahiUiUpdateType::kSummaryLoaded:
+    case MahiUiUpdateType::kSummaryAndOutlinesSectionNavigated:
+      return;
   }
 }
 
diff --git a/ash/system/mahi/refresh_banner_view.h b/ash/system/mahi/refresh_banner_view.h
index 8225a8a8..3bdad3f7 100644
--- a/ash/system/mahi/refresh_banner_view.h
+++ b/ash/system/mahi/refresh_banner_view.h
@@ -18,8 +18,11 @@
 
 namespace ash {
 
+class MahiUiUpdate;
+enum class VisibilityState;
+
 class ASH_EXPORT RefreshBannerView : public views::FlexLayoutView,
-                                     public MahiUiController::Observer {
+                                     public MahiUiController::Delegate {
   METADATA_HEADER(RefreshBannerView, views::FlexLayoutView)
 
  public:
@@ -36,9 +39,10 @@
   // views::FlexLayoutView:
   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
 
-  // MahiUiController::Observer:
-  void OnContentsRefreshInitiated() override;
-  void OnRefreshAvailabilityChanged(bool available) override;
+  // MahiUiController::Delegate:
+  views::View* GetView() override;
+  bool GetViewVisibility(VisibilityState state) const override;
+  void OnUpdated(const MahiUiUpdate& update) override;
 
   // `ui_controller_` will outlive `this`.
   const raw_ptr<MahiUiController> ui_controller_;
diff --git a/ash/system/mahi/summary_outlines_section.cc b/ash/system/mahi/summary_outlines_section.cc
index 4ea41f6e..ee0931e6 100644
--- a/ash/system/mahi/summary_outlines_section.cc
+++ b/ash/system/mahi/summary_outlines_section.cc
@@ -62,7 +62,7 @@
 }  // namespace
 
 SummaryOutlinesSection::SummaryOutlinesSection(MahiUiController* ui_controller)
-    : MahiUiController::Observer(ui_controller), ui_controller_(ui_controller) {
+    : MahiUiController::Delegate(ui_controller), ui_controller_(ui_controller) {
   CHECK(ui_controller_);
 
   SetOrientation(views::BoxLayout::Orientation::kVertical);
@@ -132,11 +132,41 @@
 
 SummaryOutlinesSection::~SummaryOutlinesSection() = default;
 
-void SummaryOutlinesSection::OnContentsRefreshInitiated() {
-  LoadSummaryAndOutlines();
+views::View* SummaryOutlinesSection::GetView() {
+  return this;
 }
 
-void SummaryOutlinesSection::OnOutlinesLoaded(
+bool SummaryOutlinesSection::GetViewVisibility(VisibilityState state) const {
+  switch (state) {
+    case VisibilityState::kError:
+    case VisibilityState::kQuestionAndAnswer:
+      return false;
+    case VisibilityState::kSummaryAndOutlines:
+      return true;
+  }
+}
+
+void SummaryOutlinesSection::OnUpdated(const MahiUiUpdate& update) {
+  switch (update.type()) {
+    case MahiUiUpdateType::kContentsRefreshInitiated:
+      LoadSummaryAndOutlines();
+      return;
+    case MahiUiUpdateType::kOutlinesLoaded:
+      HandleOutlinesLoaded(update.GetOutlines());
+      return;
+    case MahiUiUpdateType::kSummaryLoaded:
+      HandleSummaryLoaded(update.GetSummary());
+      return;
+    case MahiUiUpdateType::kAnswerLoaded:
+    case MahiUiUpdateType::kErrorReceived:
+    case MahiUiUpdateType::kQuestionPosted:
+    case MahiUiUpdateType::kRefreshAvailabilityUpdated:
+    case MahiUiUpdateType::kSummaryAndOutlinesSectionNavigated:
+      return;
+  }
+}
+
+void SummaryOutlinesSection::HandleOutlinesLoaded(
     const std::vector<chromeos::MahiOutline>& outlines) {
   outlines_container_->RemoveAllChildViews();
   for (auto outline : outlines) {
@@ -159,21 +189,7 @@
   outlines_container_->SetVisible(false);
 }
 
-void SummaryOutlinesSection::OnStateChanged(
-    MahiUiController::State new_state,
-    const std::optional<PayloadType>& payload) {
-  switch (new_state) {
-    case MahiUiController::State::kError:
-    case MahiUiController::State::kQuestionAndAnswer:
-      SetVisible(false);
-      return;
-    case MahiUiController::State::kSummaryAndOutlines:
-      SetVisible(true);
-      return;
-  }
-}
-
-void SummaryOutlinesSection::OnSummaryLoaded(
+void SummaryOutlinesSection::HandleSummaryLoaded(
     const std::u16string& summary_text) {
   summary_label_->SetVisible(true);
   summary_label_->SetText(summary_text);
diff --git a/ash/system/mahi/summary_outlines_section.h b/ash/system/mahi/summary_outlines_section.h
index ecf5cb4..97cb10a 100644
--- a/ash/system/mahi/summary_outlines_section.h
+++ b/ash/system/mahi/summary_outlines_section.h
@@ -26,7 +26,7 @@
 
 // The view containing the summary and outlines section within the Mahi panel.
 class ASH_EXPORT SummaryOutlinesSection : public views::BoxLayoutView,
-                                          public MahiUiController::Observer {
+                                          public MahiUiController::Delegate {
   METADATA_HEADER(SummaryOutlinesSection, views::BoxLayoutView)
 
  public:
@@ -36,13 +36,14 @@
   ~SummaryOutlinesSection() override;
 
  private:
-  // MahiUiController::Observer:
-  void OnContentsRefreshInitiated() override;
-  void OnOutlinesLoaded(
-      const std::vector<chromeos::MahiOutline>& outlines) override;
-  void OnStateChanged(MahiUiController::State new_state,
-                      const std::optional<PayloadType>& payload) override;
-  void OnSummaryLoaded(const std::u16string& summary) override;
+  // MahiUiController::Delegate:
+  views::View* GetView() override;
+  bool GetViewVisibility(VisibilityState state) const override;
+  void OnUpdated(const MahiUiUpdate& update) override;
+
+  void HandleOutlinesLoaded(const std::vector<chromeos::MahiOutline>& outlines);
+
+  void HandleSummaryLoaded(const std::u16string& summary_text);
 
   // Requests summary and outlines data from `MahiManager` for the currently
   // active content and starts playing the loading animations.
diff --git a/ash/system/network/cellular_setup_notifier.cc b/ash/system/network/cellular_setup_notifier.cc
index 6fcf883f..b3c766df1 100644
--- a/ash/system/network/cellular_setup_notifier.cc
+++ b/ash/system/network/cellular_setup_notifier.cc
@@ -26,41 +26,19 @@
 namespace ash {
 namespace {
 
+using chromeos::network_config::mojom::DeviceStatePropertiesPtr;
+using chromeos::network_config::mojom::NetworkStatePropertiesPtr;
+
 const char kNotifierCellularSetup[] = "ash.cellular-setup";
 
 // Delay after OOBE until notification should be shown.
 constexpr base::TimeDelta kNotificationDelay = base::Minutes(15);
 
-bool DoesCellularDeviceExist(
-    const std::vector<
-        chromeos::network_config::mojom::DeviceStatePropertiesPtr>& devices) {
-  for (const auto& device : devices) {
-    if (device->type ==
-        chromeos::network_config::mojom::NetworkType::kCellular) {
-      return true;
-    }
-  }
-  return false;
-}
-
 void OnCellularSetupNotificationClicked() {
   Shell::Get()->system_tray_model()->client()->ShowNetworkCreate(
       ::onc::network_type::kCellular);
 }
 
-// Returns the value of kCanCellularSetupNotificationBeShown for the last active
-// user. If the last active user's PrefService is null, returns false.
-bool GetCanCellularSetupNotificationBeShown() {
-  PrefService* prefs =
-      Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  if (!prefs) {
-    // Return false because we don't want to show the notification if we're
-    // unsure if it can be shown or not.
-    return false;
-  }
-  return prefs->GetBoolean(prefs::kCanCellularSetupNotificationBeShown);
-}
-
 // Sets kCanCellularSetupNotificationBeShown to false for the last active user.
 // Returns true if the flag was successfully set, and false if not.
 bool SetCellularSetupNotificationCannotBeShown() {
@@ -102,63 +80,19 @@
 
 void CellularSetupNotifier::OnSessionStateChanged(
     session_manager::SessionState state) {
-  if (Shell::Get()->session_controller()->GetSessionState() !=
-      session_manager::SessionState::ACTIVE) {
-    timer_->Stop();
-    return;
-  }
-
-  if (!GetCanCellularSetupNotificationBeShown()) {
-    // The notification has already been shown or there is some condition that
-    // dictates that the notification shouldn't be shown.
-    return;
-  }
-
-  // Wait |kNotificationDelay| after the user logs in before attempting to show
-  // a notification. This allows the user time to set up a cellular network if
-  // they desire, and it also ensures we don't spam the user with an extra
-  // notification just after they log into their device for the first time.
-  timer_->Start(FROM_HERE, kNotificationDelay,
-                base::BindOnce(&CellularSetupNotifier::OnTimerFired,
-                               base::Unretained(this)));
+  has_active_session_ = Shell::Get()->session_controller()->GetSessionState() ==
+                        session_manager::SessionState::ACTIVE;
+  StartStopTimer();
 }
 
-void CellularSetupNotifier::OnTimerFired() {
-  timer_fired_ = true;
-  MaybeShowCellularSetupNotification();
-}
-
-void CellularSetupNotifier::OnNetworkStateListChanged() {
-  MaybeShowCellularSetupNotification();
-}
-
-void CellularSetupNotifier::OnNetworkStateChanged(
-    chromeos::network_config::mojom::NetworkStatePropertiesPtr network) {
-  if (network->type !=
-          chromeos::network_config::mojom::NetworkType::kCellular ||
-      network->type_state->get_cellular()->activation_state !=
-          chromeos::network_config::mojom::ActivationStateType::kActivated) {
-    return;
-  }
-
-  SetCellularSetupNotificationCannotBeShown();
-  message_center::MessageCenter* message_center =
-      message_center::MessageCenter::Get();
-  message_center->RemoveNotification(kCellularSetupNotificationId, false);
-}
-
-void CellularSetupNotifier::MaybeShowCellularSetupNotification() {
+void CellularSetupNotifier::OnDeviceStateListChanged() {
   remote_cros_network_config_->GetDeviceStateList(base::BindOnce(
       &CellularSetupNotifier::OnGetDeviceStateList, base::Unretained(this)));
 }
 
-void CellularSetupNotifier::OnGetDeviceStateList(
-    std::vector<chromeos::network_config::mojom::DeviceStatePropertiesPtr>
-        devices) {
-  if (!DoesCellularDeviceExist(devices)) {
-    // Save to prefs not to show this notification again so we don't keep
-    // starting the timer for a cellular-incapable device.
-    SetCellularSetupNotificationCannotBeShown();
+void CellularSetupNotifier::OnNetworkStateListChanged() {
+  // Return early if we have already seen an activated cellular network.
+  if (has_activated_cellular_network_) {
     return;
   }
   remote_cros_network_config_->GetNetworkStateList(
@@ -166,40 +100,105 @@
           chromeos::network_config::mojom::FilterType::kAll,
           chromeos::network_config::mojom::NetworkType::kCellular,
           chromeos::network_config::mojom::kNoLimit),
-      base::BindOnce(&CellularSetupNotifier::OnCellularNetworksList,
+      base::BindOnce(&CellularSetupNotifier::OnGetNetworkStateList,
                      base::Unretained(this)));
 }
 
-void CellularSetupNotifier::OnCellularNetworksList(
-    std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
-        networks) {
-  // Check if there are any activated pSIM or eSIM networks. The activation
-  // state property is set to activated for all eSIM services.
+void CellularSetupNotifier::OnNetworkStateChanged(
+    NetworkStatePropertiesPtr network) {
+  // Return early if we have already seen an activated cellular network.
+  if (has_activated_cellular_network_) {
+    return;
+  }
+  if (network->type !=
+          chromeos::network_config::mojom::NetworkType::kCellular ||
+      network->type_state->get_cellular()->activation_state !=
+          chromeos::network_config::mojom::ActivationStateType::kActivated) {
+    return;
+  }
+  has_activated_cellular_network_ = true;
+  SetCellularSetupNotificationCannotBeShown();
+  StopTimerOrHideNotification();
+}
+
+void CellularSetupNotifier::OnGetNetworkStateList(
+    std::vector<NetworkStatePropertiesPtr> networks) {
+  // Check if there are any activated pSIM or eSIM networks. If any are found
+  // the notification should not be shown.
   for (auto& network : networks) {
     if (network->type_state->get_cellular()->activation_state ==
         chromeos::network_config::mojom::ActivationStateType::kActivated) {
-      // Save to prefs not to try to show this notification again so we don't
-      // keep starting the timer for a user who already has an activated
-      // cellular network.
+      has_activated_cellular_network_ = true;
       SetCellularSetupNotificationCannotBeShown();
-      message_center::MessageCenter* message_center =
-          message_center::MessageCenter::Get();
-      message_center->RemoveNotification(kCellularSetupNotificationId, false);
+      StopTimerOrHideNotification();
       return;
     }
   }
+}
 
-  // Do not show notification if it has already been shown, or the timer
-  // has not yet been fired.
-  if (!GetCanCellularSetupNotificationBeShown() || !timer_fired_) {
+void CellularSetupNotifier::OnGetDeviceStateList(
+    std::vector<DeviceStatePropertiesPtr> devices) {
+  has_cellular_device_ = false;
+  for (auto& device : devices) {
+    if (device->type ==
+        chromeos::network_config::mojom::NetworkType::kCellular) {
+      has_cellular_device_ = true;
+    }
+  }
+  StartStopTimer();
+}
+
+void CellularSetupNotifier::StartStopTimer() {
+  PrefService* prefs =
+      Shell::Get()->session_controller()->GetLastActiveUserPrefService();
+  if (!prefs ||
+      !prefs->GetBoolean(prefs::kCanCellularSetupNotificationBeShown)) {
+    timer_->Stop();
     return;
   }
 
-  ShowCellularSetupNotification();
+  // Notifications can only be shown during an active user session.
+  if (!has_active_session_) {
+    timer_->Stop();
+    return;
+  }
+
+  // The notification should only be shown if the device is cellular-capable.
+  if (!has_cellular_device_) {
+    timer_->Stop();
+    return;
+  }
+
+  // The notification should only be shown if there isn't already an activated
+  // cellular network. Unlike the cases above, finding an activated cellular
+  // should result in the notification never being shown so we additionally
+  // update the pref.
+  if (has_activated_cellular_network_) {
+    SetCellularSetupNotificationCannotBeShown();
+    timer_->Stop();
+    return;
+  }
+  if (timer_->IsRunning()) {
+    return;
+  }
+
+  // Wait |kNotificationDelay| before attempting to show a notification. This
+  // allows the user time to set up a cellular network if they desire, and it
+  // also ensures we don't spam the user with an extra notification just after
+  // they log into their device for the first time.
+  timer_->Start(
+      FROM_HERE, kNotificationDelay,
+      base::BindOnce(&CellularSetupNotifier::ShowCellularSetupNotification,
+                     base::Unretained(this)));
 }
 
-// Shows the Cellular Setup notification except in cases where it is unable to
-// save that it will have shown the notification.
+void CellularSetupNotifier::StopTimerOrHideNotification() {
+  timer_->Stop();
+  message_center::MessageCenter* message_center =
+      message_center::MessageCenter::Get();
+  message_center->RemoveNotification(kCellularSetupNotificationId, false);
+}
+
 void CellularSetupNotifier::ShowCellularSetupNotification() {
   if (!SetCellularSetupNotificationCannotBeShown()) {
     // If we didn't successfully set the flag, don't show the notification or
@@ -227,8 +226,8 @@
 
   message_center::MessageCenter* message_center =
       message_center::MessageCenter::Get();
-  if (message_center->FindVisibleNotificationById(kCellularSetupNotificationId))
-    message_center->RemoveNotification(kCellularSetupNotificationId, false);
+  DCHECK(!message_center->FindVisibleNotificationById(
+      kCellularSetupNotificationId));
   message_center->AddNotification(std::move(notification));
 }
 
diff --git a/ash/system/network/cellular_setup_notifier.h b/ash/system/network/cellular_setup_notifier.h
index a27eace..060be95 100644
--- a/ash/system/network/cellular_setup_notifier.h
+++ b/ash/system/network/cellular_setup_notifier.h
@@ -37,42 +37,30 @@
 
  private:
   friend class CellularSetupNotifierTest;
-  FRIEND_TEST_ALL_PREFIXES(CellularSetupNotifierTest,
-                           DontShowNotificationUnfinishedOOBE);
-  FRIEND_TEST_ALL_PREFIXES(CellularSetupNotifierTest,
-                           ShowNotificationUnactivatedNetwork);
-  FRIEND_TEST_ALL_PREFIXES(CellularSetupNotifierTest,
-                           DontShowNotificationActivatedNetwork);
-  FRIEND_TEST_ALL_PREFIXES(CellularSetupNotifierTest,
-                           ShowNotificationMultipleUnactivatedNetworks);
-  FRIEND_TEST_ALL_PREFIXES(CellularSetupNotifierTest,
-                           LogOutBeforeNotificationShowsLogInAgain);
-  FRIEND_TEST_ALL_PREFIXES(CellularSetupNotifierTest,
-                           LogInAgainAfterShowingNotification);
-  FRIEND_TEST_ALL_PREFIXES(CellularSetupNotifierTest,
-                           LogInAgainAfterCheckingNonCellularDevice);
 
   // SessionObserver:
   void OnSessionStateChanged(session_manager::SessionState state) override;
 
   // CrosNetworkConfigObserver:
+  void OnDeviceStateListChanged() override;
   void OnNetworkStateListChanged() override;
   void OnNetworkStateChanged(
       chromeos::network_config::mojom::NetworkStatePropertiesPtr network)
       override;
 
-  void MaybeShowCellularSetupNotification();
-  void OnTimerFired();
+  void OnGetNetworkStateList(
+      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
+          networks);
   void OnGetDeviceStateList(
       std::vector<chromeos::network_config::mojom::DeviceStatePropertiesPtr>
           devices);
-  void OnCellularNetworksList(
-      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
-          networks);
+
+  // This function will evaluate whether the timer should be started, or whether
+  // it should be stopped if it is already running.
+  void StartStopTimer();
+
+  void StopTimerOrHideNotification();
   void ShowCellularSetupNotification();
-  void SetTimerForTesting(std::unique_ptr<base::OneShotTimer> test_timer) {
-    timer_ = std::move(test_timer);
-  }
 
   static const char kCellularSetupNotificationId[];
 
@@ -81,8 +69,16 @@
   mojo::Receiver<chromeos::network_config::mojom::CrosNetworkConfigObserver>
       cros_network_config_observer_receiver_{this};
 
+  // Notifications can only be shown when there is an active session.
+  bool has_active_session_{false};
+  // The cellular device may not be exposed immediately upon startup or login.
+  // We keep track of whether it is exposed or not and will only start the timer
+  // if it is exposed.
+  bool has_cellular_device_{false};
+  // Whether we have seen an activated cellular network is cached to simplify
+  // the timer logic used in this class.
+  bool has_activated_cellular_network_{false};
   std::unique_ptr<base::OneShotTimer> timer_;
-  bool timer_fired_{false};
 };
 
 }  // namespace ash
diff --git a/ash/system/network/cellular_setup_notifier_unittest.cc b/ash/system/network/cellular_setup_notifier_unittest.cc
index 6c482c2..b36978a 100644
--- a/ash/system/network/cellular_setup_notifier_unittest.cc
+++ b/ash/system/network/cellular_setup_notifier_unittest.cc
@@ -12,7 +12,8 @@
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
-#include "base/timer/mock_timer.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/task_environment.h"
 #include "chromeos/ash/components/dbus/hermes/hermes_clients.h"
 #include "chromeos/ash/components/dbus/shill/shill_clients.h"
 #include "chromeos/ash/components/network/network_cert_loader.h"
@@ -29,15 +30,29 @@
 
 namespace {
 
-const char kShillManagerClientStubCellularDevice[] =
+constexpr char kShillManagerClientStubCellularDevice[] =
     "/device/stub_cellular_device";
-const char kShillManagerClientStubCellularDeviceName[] = "stub_cellular_device";
+constexpr char kShillManagerClientStubCellularDeviceName[] =
+    "stub_cellular_device";
+constexpr char kCellularNetworkWithActivationState[] = R"(
+{
+  "GUID": "cellular_guid",
+  "Type": "cellular",
+  "Technology": "LTE",
+  "State": "idle",
+  "Cellular.ActivationState": "%s"
+})";
+
+// Delay after OOBE when the notification is expected to be shown.
+constexpr base::TimeDelta kNotificationDelay = base::Minutes(15);
 
 }  // namespace
 
 class CellularSetupNotifierTest : public NoSessionAshTestBase {
  protected:
-  CellularSetupNotifierTest() = default;
+  CellularSetupNotifierTest()
+      : NoSessionAshTestBase(
+            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
   CellularSetupNotifierTest(const CellularSetupNotifierTest&) = delete;
   CellularSetupNotifierTest& operator=(const CellularSetupNotifierTest&) =
       delete;
@@ -53,15 +68,6 @@
         std::make_unique<network_config::CrosNetworkConfigTestHelper>();
 
     AshTestBase::SetUp();
-
-    auto mock_notification_timer = std::make_unique<base::MockOneShotTimer>();
-    mock_notification_timer_ = mock_notification_timer.get();
-    Shell::Get()
-        ->system_notification_controller()
-        ->cellular_setup_notifier_->SetTimerForTesting(
-            std::move(mock_notification_timer));
-
-    base::RunLoop().RunUntilIdle();
   }
 
   void TearDown() override {
@@ -74,30 +80,64 @@
     SystemTokenCertDbStorage::Shutdown();
   }
 
-  // Returns the cellular setup notification if it is shown, and null if it is
-  // not shown.
-  message_center::Notification* GetCellularSetupNotification() {
+  void AddCellularDevice() {
+    network_config_helper_->network_state_helper().AddDevice(
+        kShillManagerClientStubCellularDevice, shill::kTypeCellular,
+        kShillManagerClientStubCellularDeviceName);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  std::string ConfigureCellularService(bool activated) {
+    const std::string path =
+        network_config_helper_->network_state_helper().ConfigureService(
+            base::StringPrintf(kCellularNetworkWithActivationState,
+                               activated
+                                   ? shill::kActivationStateActivated
+                                   : shill::kActivationStateNotActivated));
+    base::RunLoop().RunUntilIdle();
+    return path;
+  }
+
+  void ActivateCellularService(const std::string& path) {
+    network_config_helper_->network_state_helper().SetServiceProperty(
+        path, shill::kActivationStateProperty,
+        base::Value(shill::kActivationStateActivated));
+    base::RunLoop().RunUntilIdle();
+  }
+
+  // Returns whether the cellular setup notification is shown.
+  bool IsNotificationShown() {
     return message_center::MessageCenter::Get()->FindVisibleNotificationById(
         CellularSetupNotifier::kCellularSetupNotificationId);
   }
 
-  void LogIn() { SimulateUserLogin("user1@test.com"); }
-
-  void LogOut() { ClearLogin(); }
-
-  void LogInAndFireTimer() {
-    LogIn();
-    EXPECT_TRUE(GetCanCellularSetupNotificationBeShown());
-
-    ASSERT_TRUE(mock_notification_timer_->IsRunning());
-    mock_notification_timer_->Fire();
-    // Wait for the async network calls to complete.
+  void LogIn() {
+    SimulateUserLogin("user1@test.com");
     base::RunLoop().RunUntilIdle();
   }
 
+  void LogOut() {
+    // Remove the notification if it is shown.
+    message_center::MessageCenter::Get()->RemoveNotification(
+        CellularSetupNotifier::kCellularSetupNotificationId, false);
+    ClearLogin();
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void FastForwardNotificationDelay() {
+    task_environment()->FastForwardBy(kNotificationDelay);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  // Returns the pref that indicates whether the notification is able to be
+  // shown; the value will be |false| if the notification has already been
+  // shown, or if we should otherwise not show the notification.
   bool GetCanCellularSetupNotificationBeShown() {
     PrefService* prefs =
         Shell::Get()->session_controller()->GetLastActiveUserPrefService();
+    if (!prefs) {
+      return false;
+    }
     return prefs->GetBoolean(prefs::kCanCellularSetupNotificationBeShown);
   }
 
@@ -107,150 +147,162 @@
     prefs->SetBoolean(prefs::kCanCellularSetupNotificationBeShown, value);
   }
 
-  // Ownership passed to Shell owned CellularSetupNotifier instance.
-  raw_ptr<base::MockOneShotTimer, DanglingUntriaged> mock_notification_timer_;
-
+ private:
   std::unique_ptr<network_config::CrosNetworkConfigTestHelper>
       network_config_helper_;
 };
 
 TEST_F(CellularSetupNotifierTest, DontShowNotificationUnfinishedOOBE) {
-  ASSERT_FALSE(mock_notification_timer_->IsRunning());
-
-  message_center::Notification* notification = GetCellularSetupNotification();
-  EXPECT_FALSE(notification);
+  FastForwardNotificationDelay();
+  EXPECT_FALSE(IsNotificationShown());
 }
 
-TEST_F(CellularSetupNotifierTest, ShowNotificationUnactivatedNetwork) {
-  network_config_helper_->network_state_helper().AddDevice(
-      kShillManagerClientStubCellularDevice, shill::kTypeCellular,
-      kShillManagerClientStubCellularDeviceName);
+TEST_F(CellularSetupNotifierTest, ShowNotificationZeroUnactivatedNetworks) {
+  AddCellularDevice();
 
-  LogInAndFireTimer();
+  LogIn();
+  FastForwardNotificationDelay();
 
-  message_center::Notification* notification = GetCellularSetupNotification();
-  EXPECT_TRUE(notification);
+  EXPECT_TRUE(IsNotificationShown());
+  EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
+}
+
+TEST_F(CellularSetupNotifierTest, ShowNotificationOneUnactivatedNetwork) {
+  AddCellularDevice();
+  ConfigureCellularService(/*activated=*/false);
+
+  LogIn();
+  FastForwardNotificationDelay();
+
+  EXPECT_TRUE(IsNotificationShown());
+  EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
+}
+
+TEST_F(CellularSetupNotifierTest, ShowNotificationTwoUnactivatedNetworks) {
+  AddCellularDevice();
+  ConfigureCellularService(/*activated=*/false);
+  ConfigureCellularService(/*activated=*/false);
+
+  LogIn();
+  FastForwardNotificationDelay();
+
+  EXPECT_TRUE(IsNotificationShown());
   EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
 }
 
 TEST_F(CellularSetupNotifierTest, DontShowNotificationActivatedNetwork) {
-  network_config_helper_->network_state_helper().AddDevice(
-      kShillManagerClientStubCellularDevice, shill::kTypeCellular,
-      kShillManagerClientStubCellularDeviceName);
-  const std::string& cellular_path_ =
-      network_config_helper_->network_state_helper().ConfigureService(
-          R"({"GUID": "cellular_guid", "Type": "cellular", "Technology": "LTE",
-            "State": "idle"})");
-  network_config_helper_->network_state_helper().SetServiceProperty(
-      cellular_path_, shill::kActivationStateProperty,
-      base::Value(shill::kActivationStateActivated));
-
-  LogInAndFireTimer();
-
-  message_center::Notification* notification = GetCellularSetupNotification();
-  EXPECT_FALSE(notification);
-  EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
-}
-
-TEST_F(CellularSetupNotifierTest, ShowNotificationMultipleUnactivatedNetworks) {
-  network_config_helper_->network_state_helper().AddDevice(
-      kShillManagerClientStubCellularDevice, shill::kTypeCellular,
-      kShillManagerClientStubCellularDeviceName);
-  network_config_helper_->network_state_helper().ConfigureService(
-      R"({"GUID": "cellular_guid", "Type": "cellular", "Technology": "LTE",
-            "State": "idle"})");
-  network_config_helper_->network_state_helper().ConfigureService(
-      R"({"GUID": "cellular_guid1", "Type": "cellular", "Technology": "LTE",
-            "State": "idle"})");
-
-  LogInAndFireTimer();
-
-  message_center::Notification* notification = GetCellularSetupNotification();
-  EXPECT_TRUE(notification);
-  EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
-}
-
-TEST_F(CellularSetupNotifierTest, LogOutBeforeNotificationShowsLogInAgain) {
-  network_config_helper_->network_state_helper().AddDevice(
-      kShillManagerClientStubCellularDevice, shill::kTypeCellular,
-      kShillManagerClientStubCellularDeviceName);
+  AddCellularDevice();
+  const std::string& cellular_path =
+      ConfigureCellularService(/*activated=*/true);
 
   LogIn();
-  ASSERT_TRUE(mock_notification_timer_->IsRunning());
+  FastForwardNotificationDelay();
+
+  EXPECT_FALSE(IsNotificationShown());
+  EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
+}
+
+TEST_F(CellularSetupNotifierTest, LogOutBeforeNotificationShowsThenLogInAgain) {
+  AddCellularDevice();
+
+  LogIn();
+  task_environment()->FastForwardBy(kNotificationDelay - base::Minutes(1));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(GetCanCellularSetupNotificationBeShown());
 
   LogOut();
-  ASSERT_FALSE(mock_notification_timer_->IsRunning());
+  FastForwardNotificationDelay();
+  EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
 
-  LogInAndFireTimer();
+  LogIn();
+  FastForwardNotificationDelay();
 
-  message_center::Notification* notification = GetCellularSetupNotification();
-  EXPECT_TRUE(notification);
+  EXPECT_TRUE(IsNotificationShown());
   EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
 }
 
 TEST_F(CellularSetupNotifierTest, LogInAgainAfterShowingNotification) {
-  network_config_helper_->network_state_helper().AddDevice(
-      kShillManagerClientStubCellularDevice, shill::kTypeCellular,
-      kShillManagerClientStubCellularDeviceName);
+  AddCellularDevice();
 
-  LogInAndFireTimer();
+  LogIn();
+  FastForwardNotificationDelay();
 
-  message_center::Notification* notification = GetCellularSetupNotification();
-  EXPECT_TRUE(notification);
+  EXPECT_TRUE(IsNotificationShown());
   EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
 
-  message_center::MessageCenter::Get()->RemoveNotification(
-      CellularSetupNotifier::kCellularSetupNotificationId, false);
   LogOut();
   LogIn();
 
-  ASSERT_FALSE(mock_notification_timer_->IsRunning());
+  // Check that even without a delay we correctly identify that a notification
+  // was already shown.
+  EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
+  FastForwardNotificationDelay();
+  EXPECT_FALSE(IsNotificationShown());
 }
 
 TEST_F(CellularSetupNotifierTest, LogInAgainAfterCheckingNonCellularDevice) {
-  LogInAndFireTimer();
+  // Perform the logic twice to check that even after logging out and back in
+  // the notification is not shown if there is no cellular device.
+  for (int i = 0; i < 2; ++i) {
+    LogIn();
+    FastForwardNotificationDelay();
 
-  message_center::Notification* notification = GetCellularSetupNotification();
-  EXPECT_FALSE(notification);
-  EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
+    EXPECT_FALSE(IsNotificationShown());
+    EXPECT_TRUE(GetCanCellularSetupNotificationBeShown());
 
-  LogOut();
-  LogIn();
-
-  ASSERT_FALSE(mock_notification_timer_->IsRunning());
+    LogOut();
+  }
 }
 
-TEST_F(CellularSetupNotifierTest, RemoveNotificationAfterAddingNetwork) {
-  network_config_helper_->network_state_helper().AddDevice(
-      kShillManagerClientStubCellularDevice, shill::kTypeCellular,
-      kShillManagerClientStubCellularDeviceName);
+TEST_F(CellularSetupNotifierTest,
+       NotificationStillShownAfterLatentCellularDevice) {
+  LogIn();
+  FastForwardNotificationDelay();
 
-  LogInAndFireTimer();
+  EXPECT_FALSE(IsNotificationShown());
+  EXPECT_TRUE(GetCanCellularSetupNotificationBeShown());
 
-  message_center::Notification* notification = GetCellularSetupNotification();
-  EXPECT_TRUE(notification);
+  AddCellularDevice();
+  FastForwardNotificationDelay();
+
+  EXPECT_TRUE(IsNotificationShown());
+  EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
+}
+
+TEST_F(CellularSetupNotifierTest,
+       RemoveNotificationAfterAddingActivatedNetwork) {
+  AddCellularDevice();
+
+  LogIn();
+  FastForwardNotificationDelay();
+
+  EXPECT_TRUE(IsNotificationShown());
   EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
 
-  const std::string& cellular_path_ =
-      network_config_helper_->network_state_helper().ConfigureService(
-          R"({"GUID": "cellular_guid", "Type": "cellular", "Technology": "LTE",
-            "State": "idle"})");
+  const std::string& cellular_path =
+      ConfigureCellularService(/*activated=*/true);
 
-  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(IsNotificationShown());
+}
+
+TEST_F(CellularSetupNotifierTest,
+       RemoveNotificationAfterExistingNetworkBecomesActivated) {
+  AddCellularDevice();
+
+  LogIn();
+  FastForwardNotificationDelay();
+
+  EXPECT_TRUE(IsNotificationShown());
+  EXPECT_FALSE(GetCanCellularSetupNotificationBeShown());
+
+  const std::string& cellular_path =
+      ConfigureCellularService(/*activated=*/false);
 
   // Notification is not removed after adding unactivated network.
-  notification = GetCellularSetupNotification();
-  EXPECT_TRUE(notification);
+  EXPECT_TRUE(IsNotificationShown());
 
-  network_config_helper_->network_state_helper().SetServiceProperty(
-      cellular_path_, shill::kActivationStateProperty,
-      base::Value(shill::kActivationStateActivated));
-
-  base::RunLoop().RunUntilIdle();
-
-  notification = GetCellularSetupNotification();
-  EXPECT_FALSE(notification);
-  ASSERT_FALSE(mock_notification_timer_->IsRunning());
+  ActivateCellularService(cellular_path);
+  EXPECT_FALSE(IsNotificationShown());
 }
 
 }  // namespace ash
diff --git a/ash/system/time/calendar_event_fetch.cc b/ash/system/time/calendar_event_fetch.cc
index 6a01e20..5ae70f4 100644
--- a/ash/system/time/calendar_event_fetch.cc
+++ b/ash/system/time/calendar_event_fetch.cc
@@ -14,6 +14,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/time/tick_clock.h"
 #include "base/time/time.h"
+#include "google_apis/calendar/calendar_api_requests.h"
 
 #undef ENABLED_VLOG_LEVEL
 #define ENABLED_VLOG_LEVEL 1
@@ -30,13 +31,35 @@
       complete_callback_(std::move(complete_callback)),
       internal_error_callback_(std::move(internal_error_callback)),
       fetch_start_time_(base::Time::Now()),
-      timeout_(tick_clock) {
+      timeout_(tick_clock),
+      calendar_id_(google_apis::calendar::kPrimaryCalendarId) {
   SendFetchRequest();
   Shell::Get()
       ->post_login_glanceables_metrics_reporter()
       ->RecordCalendarFetch();
 }
 
+CalendarEventFetch::CalendarEventFetch(
+    const base::Time& start_of_month,
+    FetchCompleteCallback complete_callback,
+    FetchInternalErrorCallback internal_error_callback,
+    const base::TickClock* tick_clock,
+    const std::string& calendar_id,
+    const std::string& calendar_color_id)
+    : start_of_month_(start_of_month),
+      time_range_(calendar_utils::GetFetchStartEndTimes(start_of_month)),
+      complete_callback_(std::move(complete_callback)),
+      internal_error_callback_(std::move(internal_error_callback)),
+      fetch_start_time_(base::Time::Now()),
+      timeout_(tick_clock),
+      calendar_id_(calendar_id),
+      calendar_color_id_(calendar_color_id) {
+  SendFetchRequestByCalendarId();
+  Shell::Get()
+      ->post_login_glanceables_metrics_reporter()
+      ->RecordCalendarFetch();
+}
+
 CalendarEventFetch::~CalendarEventFetch() = default;
 
 void CalendarEventFetch::Cancel() {
@@ -48,13 +71,32 @@
     Cancel();
 
   CalendarClient* client = Shell::Get()->calendar_controller()->GetClient();
-  DCHECK(client);
+  CHECK(client);
 
   cancel_closure_ =
       client->GetEventList(base::BindOnce(&CalendarEventFetch::OnResultReceived,
                                           weak_factory_.GetWeakPtr()),
                            time_range_.first, time_range_.second);
-  DCHECK(cancel_closure_);
+  CHECK(cancel_closure_);
+
+  timeout_.Start(FROM_HERE, calendar_utils::kCalendarDataFetchTimeout,
+                 base::BindOnce(&CalendarEventFetch::OnTimeout,
+                                weak_factory_.GetWeakPtr()));
+}
+
+void CalendarEventFetch::SendFetchRequestByCalendarId() {
+  if (cancel_closure_) {
+    Cancel();
+  }
+
+  CalendarClient* client = Shell::Get()->calendar_controller()->GetClient();
+  CHECK(client);
+
+  cancel_closure_ = client->GetEventList(
+      base::BindOnce(&CalendarEventFetch::OnResultReceived,
+                     weak_factory_.GetWeakPtr()),
+      time_range_.first, time_range_.second, calendar_id_, calendar_color_id_);
+  CHECK(cancel_closure_);
 
   timeout_.Start(FROM_HERE, calendar_utils::kCalendarDataFetchTimeout,
                  base::BindOnce(&CalendarEventFetch::OnTimeout,
@@ -74,7 +116,8 @@
   // IMPORTANT: 'this' is NOT safe to use after `complete_callback_` has been
   // executed, as the last thing it does is destroy its
   // std::unique_ptr<CalendarEventFetch> to this object.
-  std::move(complete_callback_).Run(start_of_month_, error, events.get());
+  std::move(complete_callback_)
+      .Run(start_of_month_, calendar_id_, error, events.get());
 }
 
 void CalendarEventFetch::OnTimeout() {
@@ -84,7 +127,8 @@
   // been executed, as the last thing it does is destroy its
   // std::unique_ptr<CalendarEventFetch> to this object.
   std::move(internal_error_callback_)
-      .Run(start_of_month_, CalendarEventFetchInternalErrorCode::kTimeout);
+      .Run(start_of_month_, calendar_id_,
+           CalendarEventFetchInternalErrorCode::kTimeout);
 }
 
 }  // namespace ash
diff --git a/ash/system/time/calendar_event_fetch.h b/ash/system/time/calendar_event_fetch.h
index f28f455..7228ec8 100644
--- a/ash/system/time/calendar_event_fetch.h
+++ b/ash/system/time/calendar_event_fetch.h
@@ -27,6 +27,7 @@
   // A callback invoked when a fetch of calendar events is complete.
   using FetchCompleteCallback =
       base::OnceCallback<void(base::Time start_of_month,
+                              std::string calendar_id,
                               google_apis::ApiErrorCode error,
                               const google_apis::calendar::EventList* events)>;
 
@@ -34,12 +35,19 @@
   // to an internal error.
   using FetchInternalErrorCallback =
       base::OnceCallback<void(base::Time start_of_month,
+                              std::string calendar_id,
                               CalendarEventFetchInternalErrorCode error)>;
 
   CalendarEventFetch(const base::Time& start_of_month,
                      FetchCompleteCallback complete_callback,
                      FetchInternalErrorCallback internal_error_callback,
                      const base::TickClock* tick_clock);
+  CalendarEventFetch(const base::Time& start_of_month,
+                     FetchCompleteCallback complete_callback,
+                     FetchInternalErrorCallback internal_error_callback,
+                     const base::TickClock* tick_clock,
+                     const std::string& calendar_id,
+                     const std::string& calendar_color_id);
   CalendarEventFetch(const CalendarEventFetch& other) = delete;
   CalendarEventFetch& operator=(const CalendarEventFetch& other) = delete;
   ~CalendarEventFetch();
@@ -48,9 +56,16 @@
   void Cancel();
 
  private:
-  // Sends the request for an event list. Cancels any in-progress fetch request.
+  // Sends the request for an event list from the user's primary calendar.
+  // Cancels any in-progress fetch request.
   void SendFetchRequest();
 
+  // Sends the request for an event list given a calendar ID.
+  // Cancels any in-progress fetch request.
+  // Events on the event list without a color ID will take on the value of
+  // `calendar_color_id_`.
+  void SendFetchRequestByCalendarId();
+
   // Callback invoked when results of a fetch are available.
   void OnResultReceived(
       google_apis::ApiErrorCode error,
@@ -78,6 +93,12 @@
   // go too long without a response.
   base::OneShotTimer timeout_;
 
+  // The ID of the calendar we want to fetch events from.
+  const std::string calendar_id_;
+
+  // The color ID of the calendar we want to fetch events from.
+  const std::string calendar_color_id_;
+
   // Closure to be invoked if the request needs to be canceled.
   base::OnceClosure cancel_closure_;
 
diff --git a/ash/system/time/calendar_event_fetch_unittest.cc b/ash/system/time/calendar_event_fetch_unittest.cc
index 9237ae785..792590e2 100644
--- a/ash/system/time/calendar_event_fetch_unittest.cc
+++ b/ash/system/time/calendar_event_fetch_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <mutex>
+#include <optional>
 #include <string>
 #include <utility>
 
@@ -44,8 +45,7 @@
 
   base::OnceClosure GetCalendarList(
       google_apis::calendar::CalendarListCallback callback) override {
-    // TODO(b/308692003): Implement TestCalendarClient changes to introduce
-    // CalendarListFetch.
+    // This method should not be used during the event fetch unit test.
     return base::DoNothing();
   }
 
@@ -71,9 +71,17 @@
       const base::Time end_time,
       const std::string& calendar_id,
       const std::string& calendar_color_id) override {
-    // TODO(b/320738368): Implement TestCalendarClient changes to follow
-    // incoming CalendarEventFetch changes.
-    return base::DoNothing();
+    // Store these off.
+    start_time_ = start_time;
+    callback_ = std::move(callback);
+    calendar_id_ = calendar_id;
+
+    // By delaying the response, we make the unit tests behave a little more
+    // like a event fetch in production.  Use set_response_delay() to use a
+    // value that's different from the default.
+    StartResponseDelayTimeout();
+    return base::BindOnce(&TestCalendarClient::CancelCallback,
+                          weak_factory_.GetWeakPtr());
   }
 
   void CancelCallback() { set_api_error_code(google_apis::CANCELLED); }
@@ -131,6 +139,7 @@
 
   google_apis::calendar::CalendarEventListCallback callback_;
   base::Time start_time_;
+  std::optional<std::string> calendar_id_;
   std::unique_ptr<google_apis::calendar::EventList> event_list_;
   google_apis::ApiErrorCode api_error_code_ = google_apis::HTTP_SUCCESS;
   base::RetainingOneShotTimer fetch_response_timeout_;
@@ -160,8 +169,10 @@
 
   // Actual callback invoked when an event fetch is complete.
   void OnEventsFetched(base::Time start_of_month,
+                       std::string calendar_id,
                        google_apis::ApiErrorCode error,
                        const google_apis::calendar::EventList* events) {
+    calendar_id_ = calendar_id;
     api_error_code_ = error;
     events_fetched_count_ = 0;
     if (events)
@@ -171,7 +182,9 @@
   // Callback invoked when an event fetch failed with an internal error.
   void OnEventFetchFailedInternalError(
       base::Time start_of_month,
+      std::string calendar_id,
       CalendarEventFetchInternalErrorCode error) {
+    calendar_id_ = calendar_id;
     internal_error_code_ = error;
   }
 
@@ -196,6 +209,24 @@
     return fetch;
   }
 
+  std::unique_ptr<CalendarEventFetch> PerformFetchByCalendarId(
+      const base::Time start_of_month,
+      const std::string calendar_id,
+      const std::string calendar_color_id) {
+    std::unique_ptr<CalendarEventFetch> fetch =
+        std::make_unique<CalendarEventFetch>(
+            start_of_month,
+            base::BindRepeating(&CalendarEventFetchTest::OnEventsFetched,
+                                base::Unretained(this)),
+            base::BindRepeating(
+                &CalendarEventFetchTest::OnEventFetchFailedInternalError,
+                base::Unretained(this)),
+            task_environment()->GetMockTickClock(), calendar_id,
+            calendar_color_id);
+    return fetch;
+  }
+
+  std::optional<std::string> get_calendar_id() { return calendar_id_; }
   std::optional<int> events_fetched_count() { return events_fetched_count_; }
   std::optional<google_apis::ApiErrorCode> api_error_code() {
     return api_error_code_;
@@ -212,6 +243,7 @@
     return AccountId::FromUserEmail("user0@tray");
   }
 
+  std::optional<std::string> calendar_id_;
   std::optional<int> events_fetched_count_;
   std::optional<google_apis::ApiErrorCode> api_error_code_;
   std::optional<CalendarEventFetchInternalErrorCode> internal_error_code_;
@@ -303,6 +335,52 @@
   EXPECT_FALSE(internal_error_code().has_value());
 }
 
+TEST_F(CalendarEventFetchTest, FetchEventsForNonPrimaryCalendar) {
+  RegisterClient();
+
+  // The month for which we want to fetch events.
+  base::Time start_of_month = GetStartOfMonthFromString("01 Oct 2023 8:00 GMT");
+
+  // The ID and color ID of the calendar we want to fetch events for.
+  std::string calendar_id = "google.com_zu5cft6test@group.calendar.google.com";
+  std::string calendar_color_id = "14";
+
+  // Inject some events.
+  auto event_list = std::make_unique<google_apis::calendar::EventList>();
+  event_list->set_time_zone("Greenwich Mean Time");
+  event_list->InjectItemForTesting(calendar_test_utils::CreateEvent(
+      "id_0", "summary_0", "02 Oct 2023 17:00 GMT", "02 Oct 2023 18:00 GMT"));
+  event_list->InjectItemForTesting(calendar_test_utils::CreateEvent(
+      "id_1", "summary_1", "05 Oct 2023 19:00 GMT", "05 Oct 2023 20:00 GMT"));
+  client_.set_event_list(std::move(event_list));
+
+  std::unique_ptr<CalendarEventFetch> fetch =
+      PerformFetchByCalendarId(start_of_month, calendar_id, calendar_color_id);
+
+  // Advance time to when the fetch is complete. `fetch` can no longer be used
+  // after this.
+  task_environment()->FastForwardBy(client_.get_response_delay());
+
+  // There are two events for this month in the client, so fetch should return
+  // two events.
+  std::optional<int> count = events_fetched_count();
+  EXPECT_TRUE(count.has_value() && count.value() == 2);
+
+  // API error is HTTP_SUCCESS.
+  std::optional<google_apis::ApiErrorCode> return_error_code = api_error_code();
+  EXPECT_TRUE(return_error_code.has_value() &&
+              return_error_code == google_apis::HTTP_SUCCESS);
+
+  // No internal error.
+  EXPECT_FALSE(internal_error_code().has_value());
+
+  // The calendar ID assigned on fetch completion should equal the calendar ID
+  // passed during the creation of the fetch.
+  std::optional<std::string> fetch_calendar_id = get_calendar_id();
+  EXPECT_TRUE(fetch_calendar_id.has_value() &&
+              fetch_calendar_id == calendar_id);
+}
+
 TEST_F(CalendarEventFetchTest, ApiFailure) {
   // Specifically set up the fetch to fail with an API error.
   const google_apis::ApiErrorCode error_code = google_apis::HTTP_NOT_FOUND;
diff --git a/ash/system/time/calendar_event_list_item_view.cc b/ash/system/time/calendar_event_list_item_view.cc
index 3979c27..d2f38c1 100644
--- a/ash/system/time/calendar_event_list_item_view.cc
+++ b/ash/system/time/calendar_event_list_item_view.cc
@@ -79,6 +79,22 @@
          {"10", "5B9157"},
          {"11", "D45D5D"}});
 
+// In Multi-Calendar, events without a custom color are injected with the color
+// ID of the calendar they belong to in order to maintain a visible distinction
+// between calendars when viewing events.
+// Modified events have been prepended with a marker (`kInjectedColorIdPrefix`)
+// to indicate that the calendar color map should be used to decode the color.
+constexpr auto kCalendarHexColorCodes =
+    base::MakeFixedFlatMap<std::string_view, std::string_view>(
+        {{"c1", "ac725e"},  {"c2", "d06b64"},  {"c3", "f83a22"},
+         {"c4", "fa573c"},  {"c5", "ff7537"},  {"c6", "ffad46"},
+         {"c7", "42d692"},  {"c8", "16a765"},  {"c9", "7bd148"},
+         {"c10", "b3dc6c"}, {"c11", "fbe983"}, {"c12", "fad165"},
+         {"c13", "92e1c0"}, {"c14", "9fe1e7"}, {"c15", "9fc6e7"},
+         {"c16", "4986e7"}, {"c17", "9a9cff"}, {"c18", "b99aff"},
+         {"c19", "c2c2c2"}, {"c20", "cabdbf"}, {"c21", "cca6ac"},
+         {"c22", "f691b2"}, {"c23", "cd74e6"}, {"c24", "a47ae2"}});
+
 constexpr SkAlpha SK_Alpha38Opacity = 0x61;
 constexpr SkAlpha SK_Alpha50Opacity = 0x80;
 
@@ -90,7 +106,8 @@
   explicit CalendarEventListItemDot(std::string color_id)
       : alpha_(color_id == kPastEventsColorId ? SK_Alpha38Opacity
                                               : SK_AlphaOPAQUE) {
-    DCHECK(color_id.empty() || kEventHexColorCodes.count(color_id));
+    CHECK(color_id.empty() || kEventHexColorCodes.count(color_id) ||
+          kCalendarHexColorCodes.count(color_id));
 
     std::string_view hex_code = LookupColorId(color_id);
     base::HexStringToInt(hex_code, &color_);
@@ -115,11 +132,17 @@
 
  private:
   std::string_view LookupColorId(std::string color_id) {
-    const auto iter = kEventHexColorCodes.find(color_id);
-    if (iter == kEventHexColorCodes.end()) {
-      return kEventHexColorCodes.at(kDefaultColorId);
+    const auto event_color_iter = kEventHexColorCodes.find(color_id);
+    if (event_color_iter != kEventHexColorCodes.end()) {
+      return event_color_iter->second;
     }
-    return iter->second;
+    if (calendar_utils::IsMultiCalendarEnabled()) {
+      const auto cal_color_iter = kCalendarHexColorCodes.find(color_id);
+      if (cal_color_iter != kCalendarHexColorCodes.end()) {
+        return cal_color_iter->second;
+      }
+    }
+    return kEventHexColorCodes.at(kDefaultColorId);
   }
 
   // The color value and the opacity of the dot.
diff --git a/ash/system/time/calendar_event_list_view.cc b/ash/system/time/calendar_event_list_view.cc
index d6932ee..9f173587 100644
--- a/ash/system/time/calendar_event_list_view.cc
+++ b/ash/system/time/calendar_event_list_view.cc
@@ -254,8 +254,7 @@
 
 void CalendarEventListView::OnEventsFetched(
     const CalendarModel::FetchingStatus status,
-    const base::Time start_time,
-    const google_apis::calendar::EventList* events) {
+    const base::Time start_time) {
   if (status == CalendarModel::kSuccess &&
       start_time == calendar_utils::GetStartOfMonthUTC(
                         calendar_view_controller_->selected_date_midnight())) {
diff --git a/ash/system/time/calendar_event_list_view.h b/ash/system/time/calendar_event_list_view.h
index 17594729..9dc016a 100644
--- a/ash/system/time/calendar_event_list_view.h
+++ b/ash/system/time/calendar_event_list_view.h
@@ -34,6 +34,7 @@
   void RequestCloseButtonFocus();
 
  private:
+  friend class CalendarViewEventListViewFetchTest;
   friend class CalendarViewEventListViewTest;
   friend class CalendarViewTest;
 
@@ -42,8 +43,7 @@
 
   // CalendarModel::Observer:
   void OnEventsFetched(const CalendarModel::FetchingStatus status,
-                       const base::Time start_time,
-                       const google_apis::calendar::EventList* events) override;
+                       const base::Time start_time) override;
 
   // views::View
   void Layout(PassKey) override;
diff --git a/ash/system/time/calendar_event_list_view_unittest.cc b/ash/system/time/calendar_event_list_view_unittest.cc
index ea79337..b456397 100644
--- a/ash/system/time/calendar_event_list_view_unittest.cc
+++ b/ash/system/time/calendar_event_list_view_unittest.cc
@@ -4,17 +4,26 @@
 
 #include "ash/system/time/calendar_event_list_view.h"
 
+#include "ash/calendar/calendar_controller.h"
+#include "ash/constants/ash_pref_names.h"
+#include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/time/calendar_event_list_item_view.h"
+#include "ash/system/time/calendar_list_model.h"
+#include "ash/system/time/calendar_model.h"
 #include "ash/system/time/calendar_unittest_utils.h"
 #include "ash/system/time/calendar_utils.h"
 #include "ash/system/time/calendar_view_controller.h"
 #include "ash/test/ash_test_base.h"
+#include "base/memory/raw_ptr.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
+#include "base/time/time_override.h"
 #include "chromeos/ash/components/settings/scoped_timezone_settings.h"
+#include "google_apis/calendar/calendar_api_requests.h"
 #include "google_apis/common/api_error_codes.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/views/controls/button/label_button.h"
@@ -71,12 +80,22 @@
   return event_list;
 }
 
+const char* kCalendarId1 = "user1@email.com";
+const char* kCalendarSummary1 = "user1@email.com";
+const char* kCalendarColorId1 = "12";
+bool kCalendarSelected1 = true;
+bool kCalendarPrimary1 = true;
+
 }  // namespace
 
-class CalendarViewEventListViewTest : public AshTestBase,
-                                      public testing::WithParamInterface<bool> {
+class CalendarViewEventListViewTest
+    : public AshTestBase,
+      public testing::WithParamInterface</*multi_calendar_enabled=*/bool> {
  public:
-  CalendarViewEventListViewTest() = default;
+  CalendarViewEventListViewTest() {
+    scoped_feature_list_.InitWithFeatureState(
+        ash::features::kMultiCalendarSupport, IsMultiCalendarEnabled());
+  }
   CalendarViewEventListViewTest(const CalendarViewEventListViewTest&) = delete;
   CalendarViewEventListViewTest& operator=(
       const CalendarViewEventListViewTest&) = delete;
@@ -85,6 +104,7 @@
   void SetUp() override {
     AshTestBase::SetUp();
     controller_ = std::make_unique<CalendarViewController>();
+    calendar_model_ = Shell::Get()->system_tray_model()->calendar_model();
     widget_ = CreateFramelessTestWidget();
     widget_->SetFullscreen(true);
   }
@@ -92,28 +112,27 @@
   void TearDown() override {
     event_list_view_.reset();
     controller_.reset();
+    scoped_feature_list_.Reset();
     widget_.reset();
+
     AshTestBase::TearDown();
   }
 
+  bool IsMultiCalendarEnabled() { return GetParam(); }
+
   void CreateEventListView(base::Time date) {
     event_list_view_.reset();
     controller_->UpdateMonth(date);
-    Shell::Get()->system_tray_model()->calendar_model()->OnEventsFetched(
-        calendar_utils::GetStartOfMonthUTC(date),
-        google_apis::ApiErrorCode::HTTP_SUCCESS, CreateMockEventList().get());
+    calendar_model_->OnEventsFetched(calendar_utils::GetStartOfMonthUTC(date),
+                                     google_apis::calendar::kPrimaryCalendarId,
+                                     google_apis::ApiErrorCode::HTTP_SUCCESS,
+                                     CreateMockEventList().get());
     controller_->selected_date_ = date;
     event_list_view_ =
         std::make_unique<CalendarEventListView>(controller_.get());
     widget_->SetContentsView(event_list_view_.get());
   }
 
-  void RefetchEvents(base::Time start_of_month,
-                     const google_apis::calendar::EventList* events) {
-    Shell::Get()->system_tray_model()->calendar_model()->OnEventsFetched(
-        start_of_month, google_apis::HTTP_SUCCESS, events);
-  }
-
   void SetSelectedDate(base::Time date) {
     controller_->selected_date_ = date;
     controller_->ShowEventListView(/*calendar_date_cell_view=*/nullptr, date,
@@ -172,14 +191,19 @@
 
  private:
   std::unique_ptr<views::Widget> widget_;
+  std::unique_ptr<calendar_test_utils::CalendarClientTestImpl> calendar_client_;
+  raw_ptr<CalendarModel, DanglingUntriaged> calendar_model_;
   std::unique_ptr<CalendarEventListView> event_list_view_;
   std::unique_ptr<CalendarViewController> controller_;
+  base::test::ScopedFeatureList scoped_feature_list_;
   static base::Time fake_time_;
 };
 
 base::Time CalendarViewEventListViewTest::fake_time_;
 
-INSTANTIATE_TEST_SUITE_P(All, CalendarViewEventListViewTest, testing::Bool());
+INSTANTIATE_TEST_SUITE_P(MultiCalendar,
+                         CalendarViewEventListViewTest,
+                         testing::Bool());
 
 TEST_P(CalendarViewEventListViewTest, ShowEvents) {
   base::Time date;
@@ -264,41 +288,6 @@
             3);
 }
 
-TEST_P(CalendarViewEventListViewTest, RefreshEvents) {
-  base::Time date;
-  ASSERT_TRUE(base::Time::FromString("18 Nov 2021 10:00 GMT", &date));
-  CreateEventListView(date);
-
-  SetSelectedDate(date);
-
-  // With the initial event list there should be 3 events on the 18th.
-  EXPECT_EQ(3u, GetContentViewSize());
-
-  base::Time start_of_month = calendar_utils::GetStartOfMonthUTC(
-      controller()->selected_date_midnight());
-  auto event_list = std::make_unique<google_apis::calendar::EventList>();
-  event_list->InjectItemForTesting(calendar_test_utils::CreateEvent(
-      "id_4", "summary_4", "21 Nov 2021 8:30 GMT", "21 Nov 2021 9:30 GMT"));
-
-  // Calls the `OnEventsFetched` method to update the events in the model.
-  // The event list view should be re-rendered automatically with the new event
-  // list.
-  RefetchEvents(start_of_month, event_list.get());
-
-  // Shows 0 events and shows open in google calendar button after the refresh.
-  EXPECT_EQ(1u, GetEmptyContentViewSize());
-  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_CALENDAR_NO_EVENTS),
-            GetEmptyLabel());
-
-  event_list->InjectItemForTesting(calendar_test_utils::CreateEvent(
-      "id_0", "summary_0", "18 Nov 2021 8:30 GMT", "18 Nov 2021 9:30 GMT"));
-  RefetchEvents(start_of_month, event_list.get());
-
-  // Shows 1 event after the refresh.
-  EXPECT_EQ(1u, GetContentViewSize());
-  EXPECT_EQ(u"summary_0", GetSummary(0)->GetText());
-}
-
 TEST_P(CalendarViewEventListViewTest, ScrollToCurrentOrNextEvent) {
   // Sets the timezone to GMT. Otherwise in other timezones events can become
   // multi-day events that will be ignored when calculating index.
@@ -482,4 +471,197 @@
   EXPECT_EQ(scroll_view_visible_bounds.y(), first_item_bounds.y());
 }
 
+class CalendarViewEventListViewFetchTest
+    : public AshTestBase,
+      public testing::WithParamInterface</*multi_calendar_enabled=*/bool> {
+ public:
+  CalendarViewEventListViewFetchTest()
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
+    scoped_feature_list_.InitWithFeatureState(
+        ash::features::kMultiCalendarSupport, IsMultiCalendarEnabled());
+  }
+  CalendarViewEventListViewFetchTest(
+      const CalendarViewEventListViewFetchTest&) = delete;
+  CalendarViewEventListViewFetchTest& operator=(
+      const CalendarViewEventListViewFetchTest&) = delete;
+  ~CalendarViewEventListViewFetchTest() override = default;
+
+  void SetUp() override {
+    AshTestBase::SetUp();
+
+    // Register a mock `CalendarClient` to the `CalendarController`.
+    const std::string email = "user1@email.com";
+    account_id_ = AccountId::FromUserEmail(email);
+    Shell::Get()->calendar_controller()->SetActiveUserAccountIdForTesting(
+        account_id_);
+    calendar_model_ = Shell::Get()->system_tray_model()->calendar_model();
+    calendar_client_ =
+        std::make_unique<calendar_test_utils::CalendarClientTestImpl>();
+    controller_ = std::make_unique<CalendarViewController>();
+    Shell::Get()->calendar_controller()->RegisterClientForUser(
+        account_id_, calendar_client_.get());
+    Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
+        ash::prefs::kCalendarIntegrationEnabled, true);
+  }
+
+  void TearDown() override {
+    event_list_view_.reset();
+    controller_.reset();
+    scoped_feature_list_.Reset();
+    time_overrides_.reset();
+
+    AshTestBase::TearDown();
+  }
+
+  bool IsMultiCalendarEnabled() { return GetParam(); }
+
+  void WaitUntilFetched() {
+    task_environment()->FastForwardBy(base::Minutes(1));
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void CreateEventListView(base::Time date) {
+    event_list_view_.reset();
+    controller_->UpdateMonth(date);
+    calendar_model_->OnEventsFetched(calendar_utils::GetStartOfMonthUTC(date),
+                                     google_apis::calendar::kPrimaryCalendarId,
+                                     google_apis::ApiErrorCode::HTTP_SUCCESS,
+                                     CreateMockEventList().get());
+    controller_->selected_date_ = date;
+    event_list_view_ =
+        std::make_unique<CalendarEventListView>(controller_.get());
+  }
+
+  void SetCalendarList() {
+    // Sets a mock calendar list.
+    std::list<std::unique_ptr<google_apis::calendar::SingleCalendar>> calendars;
+    calendars.push_back(calendar_test_utils::CreateCalendar(
+        kCalendarId1, kCalendarSummary1, kCalendarColorId1, kCalendarSelected1,
+        kCalendarPrimary1));
+    calendar_client_->SetCalendarList(
+        calendar_test_utils::CreateMockCalendarList(std::move(calendars)));
+  }
+
+  void SetEventList(std::unique_ptr<google_apis::calendar::EventList> events) {
+    calendar_client_->SetEventList(std::move(events));
+  }
+
+  void FetchCalendars() {
+    Shell::Get()->system_tray_model()->calendar_list_model()->FetchCalendars();
+    WaitUntilFetched();
+  }
+
+  void RefetchEvents(base::Time start_of_month) {
+    calendar_model_->FetchEvents(start_of_month);
+    WaitUntilFetched();
+  }
+
+  void SetTodayFromTime(base::Time date) {
+    std::set<base::Time> months = calendar_utils::GetSurroundingMonthsUTC(
+        date, calendar_utils::kNumSurroundingMonthsCached);
+
+    calendar_model_->non_prunable_months_.clear();
+    // Non-prunable months are today's date and the two surrounding months.
+    calendar_model_->AddNonPrunableMonths(months);
+  }
+
+  void SetSelectedDate(base::Time date) {
+    controller_->selected_date_ = date;
+    controller_->ShowEventListView(/*calendar_date_cell_view=*/nullptr, date,
+                                   /*row_index=*/0);
+  }
+
+  void UpdateEventList() { event_list_view_->UpdateListItems(); }
+
+  views::View* content_view() { return event_list_view_->content_view_; }
+  CalendarViewController* controller() { return controller_.get(); }
+
+  views::View* GetSameDayEventsContainer() {
+    views::View* container =
+        content_view()->GetViewByID(kEventListSameDayEventsContainer);
+    CHECK(container);
+
+    return container;
+  }
+
+  views::Label* GetSummary(int child_index) {
+    return static_cast<views::Label*>(
+        static_cast<CalendarEventListItemView*>(
+            GetSameDayEventsContainer()->children()[child_index])
+            ->GetViewByID(kSummaryLabelID));
+  }
+
+  std::u16string GetEmptyLabel() {
+    return static_cast<views::LabelButton*>(
+               content_view()->children()[0]->children()[0])
+        ->GetText();
+  }
+
+  size_t GetContentViewSize() {
+    return GetSameDayEventsContainer()->children().size();
+  }
+
+  size_t GetEmptyContentViewSize() { return content_view()->children().size(); }
+
+ private:
+  std::unique_ptr<base::subtle::ScopedTimeClockOverrides> time_overrides_;
+  std::unique_ptr<views::Widget> widget_;
+  std::unique_ptr<calendar_test_utils::CalendarClientTestImpl> calendar_client_;
+  raw_ptr<CalendarModel, DanglingUntriaged> calendar_model_;
+  std::unique_ptr<CalendarEventListView> event_list_view_;
+  std::unique_ptr<CalendarViewController> controller_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  AccountId account_id_;
+};
+
+INSTANTIATE_TEST_SUITE_P(MultiCalendar,
+                         CalendarViewEventListViewFetchTest,
+                         testing::Bool());
+
+TEST_P(CalendarViewEventListViewFetchTest, RefreshEvents) {
+  base::Time date;
+  ASSERT_TRUE(base::Time::FromString("18 Nov 2021 10:00 GMT", &date));
+  CreateEventListView(date);
+
+  SetSelectedDate(date);
+  SetTodayFromTime(date);
+
+  // With the initial event list there should be 3 events on the 18th.
+  EXPECT_EQ(3u, GetContentViewSize());
+
+  if (IsMultiCalendarEnabled()) {
+    // Set and fetch a calendar list so FetchEvents can create an event fetch.
+    SetCalendarList();
+    FetchCalendars();
+  }
+
+  base::Time start_of_month = calendar_utils::GetStartOfMonthUTC(
+      controller()->selected_date_midnight());
+  auto event_list = std::make_unique<google_apis::calendar::EventList>();
+  event_list->InjectItemForTesting(calendar_test_utils::CreateEvent(
+      "id_4", "summary_4", "21 Nov 2021 8:30 GMT", "21 Nov 2021 9:30 GMT"));
+  SetEventList(std::move(event_list));
+
+  // Calls the `FetchEvents` method to update the events in the model.
+  // The event list view should be re-rendered automatically with the new event
+  // list.
+  RefetchEvents(start_of_month);
+
+  // Shows 0 events and shows open in google calendar button after the refresh.
+  EXPECT_EQ(1u, GetEmptyContentViewSize());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_CALENDAR_NO_EVENTS),
+            GetEmptyLabel());
+
+  auto event_list2 = std::make_unique<google_apis::calendar::EventList>();
+  event_list2->InjectItemForTesting(calendar_test_utils::CreateEvent(
+      "id_0", "summary_0", "18 Nov 2021 8:30 GMT", "18 Nov 2021 9:30 GMT"));
+  SetEventList(std::move(event_list2));
+
+  RefetchEvents(start_of_month);
+
+  // Shows 1 event after the refresh.
+  EXPECT_EQ(1u, GetContentViewSize());
+  EXPECT_EQ(u"summary_0", GetSummary(0)->GetText());
+}
+
 }  // namespace ash
diff --git a/ash/system/time/calendar_list_model.h b/ash/system/time/calendar_list_model.h
index 62069d9..b02dfc9 100644
--- a/ash/system/time/calendar_list_model.h
+++ b/ash/system/time/calendar_list_model.h
@@ -59,12 +59,24 @@
   // Returns true if the calendar list is currently being fetched.
   bool get_fetch_in_progress() { return fetch_in_progress_; }
 
+  // Returns true if the calendar list is currently cached and there is no
+  // calendar list fetch in progress.
+  bool list_cached_and_no_fetch_in_progress() {
+    return (is_cached_ && !fetch_in_progress_);
+  }
+
   // Returns the currently cached calendar list.
   // The calendar list should have at least one entry (for the primary calendar)
   // if the account that authorized the fetch uses Google Calendar.
   CalendarList GetCachedCalendarList();
 
  private:
+  // For unit tests.
+  friend class CalendarUpNextViewPixelTest;
+  friend class CalendarViewWithUpNextViewAnimationTest;
+  friend class CalendarViewWithUpNextViewTest;
+  friend class PostLoginGlanceablesMetricsRecorderTest;
+
   // A callback invoked when the calendar list fetch is complete. If the fetch
   // was successful, the cached calendar list is replaced. If the fetch failed
   // and a calendar list is already cached, the calendar list is not modified.
diff --git a/ash/system/time/calendar_model.cc b/ash/system/time/calendar_model.cc
index 2424ed9..84aa259 100644
--- a/ash/system/time/calendar_model.cc
+++ b/ash/system/time/calendar_model.cc
@@ -5,6 +5,7 @@
 #include "ash/system/time/calendar_model.h"
 
 #include <stdlib.h>
+
 #include <cstddef>
 #include <memory>
 
@@ -12,7 +13,9 @@
 #include "ash/calendar/calendar_controller.h"
 #include "ash/constants/ash_features.h"
 #include "ash/shell.h"
+#include "ash/system/model/system_tray_model.h"
 #include "ash/system/time/calendar_event_fetch.h"
+#include "ash/system/time/calendar_list_model.h"
 #include "ash/system/time/calendar_utils.h"
 #include "base/check.h"
 #include "base/containers/contains.h"
@@ -20,6 +23,7 @@
 #include "base/functional/bind.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
+#include "google_apis/calendar/calendar_api_requests.h"
 #include "google_apis/calendar/calendar_api_response_types.h"
 #include "google_apis/common/api_error_codes.h"
 
@@ -207,6 +211,12 @@
   // Destroy all outstanding fetch requests.
   pending_fetches_.clear();
 
+  // Destroy the fetch error codes.
+  fetch_error_codes_.clear();
+
+  // Destroy the fetch completion indicators.
+  events_have_fetched_.clear();
+
   // Destroy the set of months that have been fetched.
   months_fetched_.clear();
 
@@ -235,11 +245,32 @@
   mru_months_.clear();
 }
 
+bool CalendarModel::MonthHasEvents(const base::Time start_of_month) {
+  if (base::Contains(event_months_, start_of_month)) {
+    for (auto it = event_months_[start_of_month].begin();
+         it != event_months_[start_of_month].end(); it++) {
+      if (!it->second.empty()) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
 void CalendarModel::UploadLifetimeMetrics() {
   base::UmaHistogramCounts100000(
       "Ash.Calendar.FetchEvents.TotalCacheSizeMonths", event_months_.size());
 }
 
+void CalendarModel::MaybeFetchEvents(base::Time start_of_month) {
+  if (Shell::Get()
+          ->system_tray_model()
+          ->calendar_list_model()
+          ->list_cached_and_no_fetch_in_progress()) {
+    FetchEvents(start_of_month);
+  }
+}
+
 void CalendarModel::FetchEvents(base::Time start_of_month) {
   // Early return if it's not a valid user/user-session.
   if (!calendar_utils::ShouldFetchCalendarData()) {
@@ -260,13 +291,52 @@
     return;
   }
 
-  // Erase any outstanding fetch for this month.
-  pending_fetches_.erase(start_of_month);
+  // Reset fetch helpers before the new fetch(es).
+  fetch_error_codes_.erase(start_of_month);
+  if (base::Contains(non_prunable_months_, start_of_month)) {
+    events_have_fetched_.insert_or_assign(start_of_month, false);
+  }
 
-  // Construct a unique_ptr for the fetch request, and transfer ownership to
-  // `pending_fetches_`.
-  pending_fetches_.emplace(
-      start_of_month,
+  if (calendar_utils::IsMultiCalendarEnabled()) {
+    ash::CalendarList calendar_list = Shell::Get()
+                                          ->system_tray_model()
+                                          ->calendar_list_model()
+                                          ->GetCachedCalendarList();
+
+    if (!calendar_list.empty()) {
+      // Create event fetch requests for up to `kMultipleCalendarsLimit`
+      // calendars. Expects a calendar list trimmed to be within the calendar
+      // limit.
+      for (const auto& calendar : calendar_list) {
+        // Create event fetch request for the calendar and transfer ownership to
+        // `pending_fetches_`.
+        pending_fetches_[start_of_month][calendar.id()] =
+            std::make_unique<CalendarEventFetch>(
+                start_of_month,
+                /*complete_callback =*/
+                base::BindRepeating(&CalendarModel::OnEventsFetched,
+                                    base::Unretained(this)),
+                /*internal_error_callback_ =*/
+                base::BindRepeating(
+                    &CalendarModel::OnEventFetchFailedInternalError,
+                    base::Unretained(this)),
+                /*tick_clock =*/nullptr, calendar.id(), calendar.color_id());
+      }
+    } else {
+      // The user has no selected calendars, so notify observers to remove the
+      // loading bar.
+      for (auto& observer : observers_) {
+        observer.OnEventsFetched(kNever, start_of_month);
+      }
+    }
+  } else {
+    FetchPrimaryCalendarEvents(start_of_month);
+  }
+}
+
+void CalendarModel::FetchPrimaryCalendarEvents(
+    const base::Time start_of_month) {
+  pending_fetches_[start_of_month][google_apis::calendar::kPrimaryCalendarId] =
       std::make_unique<CalendarEventFetch>(
           start_of_month,
           /*complete_callback=*/
@@ -275,16 +345,25 @@
           /*internal_error_callback_=*/
           base::BindRepeating(&CalendarModel::OnEventFetchFailedInternalError,
                               base::Unretained(this)),
-          /*tick_clock=*/nullptr));
+          /*tick_clock=*/nullptr);
 }
 
 void CalendarModel::CancelFetch(const base::Time& start_of_month) {
   if (base::Contains(pending_fetches_, start_of_month)) {
-    // Note that the `CalendarEventFetch` here will be removed from
-    // `pending_fetches_` in `OnEventsFetched`, which will receive an error code
-    // of `google_apis::CANCELLED` and an empty event list, so there's no need
-    // to remove it here.
-    pending_fetches_[start_of_month]->Cancel();
+    for (auto it = pending_fetches_[start_of_month].begin();
+         it != pending_fetches_[start_of_month].end(); it++) {
+      it->second->Cancel();
+    }
+    // We want to wait until after fetches have been cancelled to erase
+    // `pending_fetches` for this month.
+    pending_fetches_.erase(start_of_month);
+    // This method might be called after some events have been fetched. For
+    // prunable months, to prevent event storage from being partially populated
+    // and displayed, we delete all stored events for the month.
+    if (!base::Contains(non_prunable_months_, start_of_month)) {
+      event_months_.erase(start_of_month);
+      months_fetched_.erase(start_of_month);
+    }
   }
 }
 
@@ -305,37 +384,60 @@
   return list.size();
 }
 
+void CalendarModel::NotifyObservers(base::Time start_of_month) {
+  // If at least one of the month's fetches succeeded, we emit kSuccess.
+  // Otherwise, emit kNever to stop the loading animation.
+  if (fetch_error_codes_[start_of_month].count(google_apis::HTTP_SUCCESS)) {
+    for (auto& observer : observers_) {
+      observer.OnEventsFetched(kSuccess, start_of_month);
+    }
+  } else {
+    for (auto& observer : observers_) {
+      observer.OnEventsFetched(kNever, start_of_month);
+    }
+  }
+}
+
 void CalendarModel::OnEventsFetched(
     base::Time start_of_month,
+    std::string calendar_id,
     google_apis::ApiErrorCode error,
     const google_apis::calendar::EventList* events) {
   base::UmaHistogramSparse("Ash.Calendar.FetchEvents.Result", error);
-  if (error != google_apis::HTTP_SUCCESS) {
-    // Request is no longer outstanding, so it can be destroyed.
-    pending_fetches_.erase(start_of_month);
-    // Notify observers that we had an error with the events fetched.
-    // Right now the `kError` status isn't handled in any way so we just emit
-    // `kNever` to stop the loading animation displaying.
-    // TODO(https://crbug.com/1298187): Possibly respond further based on the
-    // specific error code, retry in some cases, etc.
-    for (auto& observer : observers_) {
-      observer.OnEventsFetched(kNever, start_of_month, events);
-    }
 
+  fetch_error_codes_[start_of_month].emplace(error);
+
+  if (error == google_apis::CANCELLED) {
     return;
   }
 
-  // Keep us within storage limits.
+  if (error != google_apis::HTTP_SUCCESS) {
+    pending_fetches_[start_of_month].erase(calendar_id);
+    if (pending_fetches_[start_of_month].empty()) {
+      NotifyObservers(start_of_month);
+    }
+    return;
+  }
+
   PruneEventCache();
 
-  // Clear out this month's events, we're about replace them.
-  event_months_.erase(start_of_month);
+  // If this is the first fetch that has returned for a non-prunable month,
+  // clear pre-existing event storage and indicate that some new events have
+  // fetched.
+  if (base::Contains(non_prunable_months_, start_of_month) &&
+      !events_have_fetched_[start_of_month]) {
+    event_months_.erase(start_of_month);
+    events_have_fetched_[start_of_month] = true;
+  }
 
-  if (!events || events->items().empty()) {
-    // Even though `start_of_month` has no events, insert an empty map to
-    // indicate a successful fetch.
-    SingleMonthEventMap empty_event_map;
-    event_months_.emplace(start_of_month, empty_event_map);
+  // If there are no events for the current calendar and the event map for
+  // the month does not yet exist, insert an empty map. Otherwise, we do
+  // not want to overwrite events from previously fetched calendars.
+  if ((!events || events->items().empty())) {
+    if (!base::Contains(event_months_, start_of_month)) {
+      SingleMonthEventMap empty_event_map;
+      event_months_.emplace(start_of_month, empty_event_map);
+    }
     PromoteMonth(start_of_month);
   } else {
     // Store the incoming events.
@@ -353,34 +455,30 @@
     }
   }
 
-  // Notify observers.
-  for (auto& observer : observers_) {
-    observer.OnEventsFetched(kSuccess, start_of_month, events);
-  }
-
-  // Month has officially been fetched.
-  months_fetched_.emplace(start_of_month);
-
   // Request is no longer outstanding, so it can be destroyed.
-  pending_fetches_.erase(start_of_month);
+  pending_fetches_[start_of_month].erase(calendar_id);
 
-  // Record the size of the month, and the total number of months.
-  base::UmaHistogramCounts1M("Ash.Calendar.FetchEvents.SingleMonthSize",
-                             GetEventMapSize(event_months_[start_of_month]));
+  if (pending_fetches_[start_of_month].empty()) {
+    NotifyObservers(start_of_month);
+
+    months_fetched_.emplace(start_of_month);
+
+    // Record the size of the month, and the total number of months.
+    base::UmaHistogramCounts1M("Ash.Calendar.FetchEvents.SingleMonthSize",
+                               GetEventMapSize(event_months_[start_of_month]));
+  }
 }
 
 void CalendarModel::OnEventFetchFailedInternalError(
     base::Time start_of_month,
+    std::string calendar_id,
     CalendarEventFetchInternalErrorCode error) {
   // Request is no longer outstanding, so it can be destroyed.
-  pending_fetches_.erase(start_of_month);
-  // Notify observers of timeout fetching events for given `start_of_month`.
-  // Right now timeouts aren't handled in any way so we just emit `onTimeout` to
-  // stop the loading animation displaying.
-  // TODO(https://crbug.com/1298187): May need to respond further based on the
+  pending_fetches_[start_of_month].erase(calendar_id);
+  // TODO(b/40822782): May need to respond further based on the
   // specific error code, retry in some cases, etc.
-  for (auto& observer : observers_) {
-    observer.OnTimeout(start_of_month);
+  if (pending_fetches_[start_of_month].empty()) {
+    NotifyObservers(start_of_month);
   }
 }
 
@@ -541,20 +639,20 @@
 }
 
 CalendarModel::FetchingStatus CalendarModel::FindFetchingStatus(
-    base::Time start_time) const {
+    base::Time start_time) {
   if (!calendar_utils::ShouldFetchCalendarData()) {
     return kNa;
   }
 
-  if (pending_fetches_.count(start_time)) {
-    if (event_months_.count(start_time)) {
+  if (!pending_fetches_[start_time].empty()) {
+    if (months_fetched_.count(start_time)) {
       return kRefetching;
     }
 
     return kFetching;
   }
 
-  if (event_months_.count(start_time)) {
+  if (months_fetched_.count(start_time)) {
     return kSuccess;
   }
 
diff --git a/ash/system/time/calendar_model.h b/ash/system/time/calendar_model.h
index 79f632b1..10cffdf 100644
--- a/ash/system/time/calendar_model.h
+++ b/ash/system/time/calendar_model.h
@@ -72,14 +72,9 @@
 
   class Observer : public base::CheckedObserver {
    public:
-    // Invoked when a set of events has been fetched.
-    virtual void OnEventsFetched(
-        const FetchingStatus status,
-        const base::Time start_time,
-        const google_apis::calendar::EventList* events) {}
-
-    // Invoked when an internal timeout has occurred.
-    virtual void OnTimeout(base::Time start_of_month) {}
+    // Invoked when a month of events has been fetched.
+    virtual void OnEventsFetched(const FetchingStatus status,
+                                 const base::Time start_time) {}
   };
 
   void AddObserver(Observer* observer);
@@ -92,6 +87,10 @@
   // Clears out all events that start in a non-prunable month.
   void ClearAllPrunableEvents();
 
+  // Returns true if the event storage for a given month is populated with at
+  // least one event.
+  bool MonthHasEvents(const base::Time start_of_month);
+
   // Logs to UMA all event fetch metrics recorded over the lifetime of a
   // calendar session.
   void UploadLifetimeMetrics();
@@ -102,9 +101,19 @@
   // Adds every month in `months` to the set of non-prunable months.
   void AddNonPrunableMonths(const std::set<base::Time>& months);
 
-  // Requests events that of the passed in `start_of_month`.
+  // Requests events for the month starting at `start_of_month` if there is not
+  // currently a calendar list fetch in progress.
+  void MaybeFetchEvents(base::Time start_of_month);
+
+  // Requests events for the month starting at `start_of_month`. If there is no
+  // calendar list, or Multi-Calendar is disabled, only primary calendar events
+  // are fetched.
   void FetchEvents(base::Time start_of_month);
 
+  // Requests events for the month starting at `start_of_month` for the primary
+  // calendar.
+  void FetchPrimaryCalendarEvents(const base::Time start_of_month);
+
   // Cancels any pending event fetch for `start_of_month`.
   void CancelFetch(const base::Time& start_of_month);
 
@@ -127,7 +136,7 @@
       base::Time now_local) const;
 
   // Checks the `FetchingStatus` of a given start time.
-  FetchingStatus FindFetchingStatus(base::Time start_time) const;
+  FetchingStatus FindFetchingStatus(base::Time start_time);
 
   // Redistributes all the fetched events to the date map with the
   // time difference. This method is only called when there's a timezone change.
@@ -143,6 +152,7 @@
   friend class CalendarUpNextViewTest;
   friend class CalendarViewPixelTest;
   friend class CalendarViewAnimationTest;
+  friend class CalendarViewEventListViewFetchTest;
   friend class CalendarViewEventListViewTest;
   friend class CalendarViewTest;
   friend class CalendarViewWithUpNextViewAnimationTest;
@@ -175,14 +185,19 @@
   // most-recently-used to least-recently-used.
   void PromoteMonth(base::Time start_of_month);
 
+  // Based on error(s) received during a month's fetch(es), notify observers.
+  void NotifyObservers(base::Time start_of_month);
+
   // Actual callback invoked when an event fetch is complete.
   void OnEventsFetched(base::Time start_of_month,
+                       std::string calendar_id,
                        google_apis::ApiErrorCode error,
                        const google_apis::calendar::EventList* events);
 
   // Callback invoked when an event fetch failed with an internal error.
   void OnEventFetchFailedInternalError(
       base::Time start_of_month,
+      std::string calendar_id,
       CalendarEventFetchInternalErrorCode error);
 
   // Internal storage for fetched events, with each fetched month having a
@@ -198,8 +213,20 @@
   // Set of months we've already fetched.
   std::set<base::Time> months_fetched_;
 
-  // All fetch requests that are still in-progress.
-  std::map<base::Time, std::unique_ptr<CalendarEventFetch>> pending_fetches_;
+  // All fetch requests that are still in-progress. Maps each start of month
+  // timestamp to a map linking each calendar ID to a CalendarEventFetch
+  // object.
+  std::map<base::Time,
+           std::map<std::string, std::unique_ptr<CalendarEventFetch>>>
+      pending_fetches_;
+
+  // Maps a month to a set of error codes returned by the month's event
+  // fetches.
+  std::map<base::Time, std::set<google_apis::ApiErrorCode>> fetch_error_codes_;
+
+  // Maps a non-prunable month to an indicator that equals true if new event
+  // fetches for the month have completed successfully.
+  std::map<base::Time, bool> events_have_fetched_;
 
   ScopedSessionObserver session_observer_;
 
diff --git a/ash/system/time/calendar_model_unittest.cc b/ash/system/time/calendar_model_unittest.cc
index ad3026a..8858989 100644
--- a/ash/system/time/calendar_model_unittest.cc
+++ b/ash/system/time/calendar_model_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <cstddef>
 #include <iterator>
+#include <list>
 #include <memory>
 #include <string>
 #include <tuple>
@@ -19,6 +20,8 @@
 #include "ash/public/cpp/session/user_info.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
+#include "ash/system/model/system_tray_model.h"
+#include "ash/system/time/calendar_list_model.h"
 #include "ash/system/time/calendar_unittest_utils.h"
 #include "ash/system/time/calendar_utils.h"
 #include "ash/test/ash_test_base.h"
@@ -26,6 +29,7 @@
 #include "base/ranges/algorithm.h"
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "chromeos/ash/components/settings/scoped_timezone_settings.h"
 #include "components/user_manager/user_type.h"
@@ -38,21 +42,22 @@
 
 using ::google_apis::calendar::CalendarEvent;
 using ::google_apis::calendar::EventList;
+using ::google_apis::calendar::SingleCalendar;
 
 const char* kStartTime0 = "23 Oct 2009 11:30 GMT";
 const char* kEndTime0 = "23 Oct 2009 12:30 GMT";
 const char* kId0 = "id_0";
 const char* kSummary0 = "summary_0";
-const char* kStartTime1 = "23 Nov 2009 07:30 GMT";
-const char* kEndTime1 = "23 Nov 2009 08:30 GMT";
+const char* kStartTime1 = "19 Oct 2009 07:30 GMT";
+const char* kEndTime1 = "19 Oct 2009 08:30 GMT";
 const char* kId1 = "id_1";
 const char* kSummary1 = "summary_1";
-const char* kStartTime2 = "23 Dec 2009 11:30 GMT";
-const char* kEndTime2 = "23 Dec 2009 12:30 GMT";
+const char* kStartTime2 = "15 Oct 2009 11:30 GMT";
+const char* kEndTime2 = "15 Oct 2009 12:30 GMT";
 const char* kId2 = "id_2";
 const char* kSummary2 = "summary_2";
-const char* kStartTime3 = "23 Jan 2010 11:30 GMT";
-const char* kEndTime3 = "23 Jan 2010 12:30 GMT";
+const char* kStartTime3 = "14 Oct 2009 11:30 GMT";
+const char* kEndTime3 = "14 Oct 2009 12:30 GMT";
 const char* kId3 = "id_3";
 const char* kSummary3 = "summary_3";
 const char* kStartTime4 = "23 Feb 2010 11:30 GMT";
@@ -73,6 +78,12 @@
 const char* kSummary13 = "summary_13";
 const char* kBaseStartTime = "01 Oct 2009 00:00 GMT";
 
+const char* kCalendarId1 = "user1@email.com";
+const char* kCalendarSummary1 = "user1@email.com";
+const char* kCalendarColorId1 = "12";
+bool kCalendarSelected1 = true;
+bool kCalendarPrimary1 = true;
+
 }  // namespace
 
 class CalendarModelUtilsTest : public AshTestBase {
@@ -151,10 +162,15 @@
   EXPECT_TRUE(base::Contains(months, start_of_next_month_3));
 }
 
-class CalendarModelTest : public AshTestBase {
+class CalendarModelTest
+    : public AshTestBase,
+      public testing::WithParamInterface</*multi_calendar_enabled=*/bool> {
  public:
   CalendarModelTest()
-      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
+    scoped_feature_list_.InitWithFeatureState(
+        ash::features::kMultiCalendarSupport, IsMultiCalendarEnabled());
+  }
 
   CalendarModelTest(const CalendarModelTest& other) = delete;
   CalendarModelTest& operator=(const CalendarModelTest& other) = delete;
@@ -175,15 +191,36 @@
         account_id, calendar_client_.get());
     Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
         ash::prefs::kCalendarIntegrationEnabled, true);
+
+    if (IsMultiCalendarEnabled()) {
+      FetchCalendars();
+    }
   }
 
   void TearDown() override {
     time_overrides_.reset();
     calendar_model_.reset();
+    scoped_feature_list_.Reset();
 
     AshTestBase::TearDown();
   }
 
+  bool IsMultiCalendarEnabled() { return GetParam(); }
+
+  void FetchCalendars() {
+    // Set a mock calendar list.
+    std::list<std::unique_ptr<google_apis::calendar::SingleCalendar>> calendars;
+    calendars.push_back(calendar_test_utils::CreateCalendar(
+        kCalendarId1, kCalendarSummary1, kCalendarColorId1, kCalendarSelected1,
+        kCalendarPrimary1));
+    calendar_client_->SetCalendarList(
+        calendar_test_utils::CreateMockCalendarList(std::move(calendars)));
+
+    // Start the calendar list fetch and fast forward until it is complete.
+    calendar_list_model()->FetchCalendars();
+    WaitUntilFetched();
+  }
+
   int EventsNumberOfDay(const char* day, SingleDayEventList* events) {
     base::Time day_base = calendar_test_utils::GetTimeFromString(day);
 
@@ -328,7 +365,9 @@
   void MockOnEventsFetched(base::Time start_of_month,
                            google_apis::ApiErrorCode error,
                            const google_apis::calendar::EventList* events) {
-    calendar_model_->OnEventsFetched(start_of_month, error, events);
+    calendar_model_->OnEventsFetched(start_of_month,
+                                     google_apis::calendar::kPrimaryCalendarId,
+                                     error, events);
   }
 
   base::Time now() { return now_; }
@@ -341,6 +380,10 @@
     return calendar_model_->event_months_;
   }
 
+  CalendarListModel* calendar_list_model() {
+    return Shell::Get()->system_tray_model()->calendar_list_model();
+  }
+
   CalendarModel* calendar_model() { return calendar_model_.get(); }
 
   std::unique_ptr<base::subtle::ScopedTimeClockOverrides> time_overrides_;
@@ -348,15 +391,23 @@
   std::unique_ptr<CalendarModel> calendar_model_;
   std::unique_ptr<calendar_test_utils::CalendarClientTestImpl> calendar_client_;
   base::Time now_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-TEST_F(CalendarModelTest, FetchingSuccessfullyWithOneEvent) {
+INSTANTIATE_TEST_SUITE_P(MultiCalendar, CalendarModelTest, testing::Bool());
+
+TEST_P(CalendarModelTest, FetchingSuccessfullyWithOneEvent) {
   // All events will be distributed by the system timezone. If no timezone is
   // set, the test will run with the local default timezone which might cause a
   // test failure. So here sets the timezone to "GMT", and the same for all the
   // tests in this file.
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
 
+  if (IsMultiCalendarEnabled()) {
+    EXPECT_TRUE(calendar_list_model()->get_is_cached());
+    EXPECT_EQ(1u, calendar_list_model()->GetCachedCalendarList().size());
+  }
+
   // Set current date to `kStartTime0`.
   SetTodayFromStr(kStartTime0);
   base::Time start_of_month = calendar_utils::GetStartOfMonthUTC(
@@ -394,6 +445,12 @@
   EXPECT_FALSE(events.empty());
   EXPECT_TRUE(events.size() == 1);
 
+  // Set an empty event list as the mock response.
+  auto event_list2 = std::make_unique<google_apis::calendar::EventList>();
+
+  // Set the event list as the response;
+  SetEventList(std::move(event_list2));
+
   // Now we do a refetch.
   calendar_model()->FetchEvents(calendar_utils::GetStartOfMonthUTC(now()));
 
@@ -405,16 +462,45 @@
 
   EXPECT_EQ(CalendarModel::kSuccess,
             calendar_model()->FindFetchingStatus(start_of_month));
+  EXPECT_EQ(0, EventsNumberOfDay(kStartTime0, &events));
+
+  std::unique_ptr<google_apis::calendar::CalendarEvent> event2 =
+      calendar_test_utils::CreateEvent(kId0, kSummary0, kStartTime0, kEndTime0);
+
+  // Set up list of events as the mock response.
+  auto event_list3 = std::make_unique<google_apis::calendar::EventList>();
+  event_list3->InjectItemForTesting(std::move(event2));
+
+  // Set the event list as the response;
+  SetEventList(std::move(event_list3));
+
+  // Now we do a refetch.
+  calendar_model()->FetchEvents(calendar_utils::GetStartOfMonthUTC(now()));
+
+  EXPECT_EQ(CalendarModel::kRefetching,
+            calendar_model()->FindFetchingStatus(start_of_month));
+  EXPECT_EQ(0, EventsNumberOfDay(kStartTime0, &events));
+
+  WaitUntilFetched();
+
+  EXPECT_EQ(CalendarModel::kSuccess,
+            calendar_model()->FindFetchingStatus(start_of_month));
+  EXPECT_EQ(1, EventsNumberOfDay(kStartTime0, &events));
 }
 
-TEST_F(CalendarModelTest, FetchingSuccessfullyWithMultiEvents) {
+TEST_P(CalendarModelTest, FetchingSuccessfullyWithMultiEvents) {
   // Sets the timezone to "GMT".
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
 
+  if (IsMultiCalendarEnabled()) {
+    EXPECT_TRUE(calendar_list_model()->get_is_cached());
+    EXPECT_EQ(1u, calendar_list_model()->GetCachedCalendarList().size());
+  }
+
   // Set current date to `kStartTime0`.
   SetTodayFromStr(kStartTime0);
 
-  // Set up list of events as the mock response.
+  // Set up list of events from the same month as the mock response.
   std::unique_ptr<google_apis::calendar::CalendarEvent> event0 =
       calendar_test_utils::CreateEvent(kId0, kSummary0, kStartTime0, kEndTime0);
   std::unique_ptr<google_apis::calendar::CalendarEvent> event1 =
@@ -487,6 +573,12 @@
   EXPECT_EQ(1, EventsNumberOfDay(kStartTime3, &events));
   EXPECT_EQ(1, EventsNumberOfDay(kStartTime13, &events));
 
+  // Set up an empty event list.
+  auto event_list2 = std::make_unique<google_apis::calendar::EventList>();
+
+  // Set this event list as the response;
+  SetEventList(std::move(event_list2));
+
   // Now we do a refetch.
   calendar_model()->FetchEvents(calendar_utils::GetStartOfMonthUTC(now()));
 
@@ -498,15 +590,21 @@
 
   EXPECT_EQ(CalendarModel::kSuccess,
             calendar_model()->FindFetchingStatus(start_of_month0));
+  EXPECT_EQ(0, EventsNumberOfDay(kStartTime0, &events));
 }
 
-TEST_F(CalendarModelTest, ChangeTimeDifference) {
+TEST_P(CalendarModelTest, ChangeTimeDifference) {
   // Sets the timezone to "America/Los_Angeles".
   ash::system::ScopedTimezoneSettings timezone_settings(u"America/Los_Angeles");
   calendar_test_utils::ScopedLibcTimeZone scoped_libc_timezone(
       "America/Los_Angeles");
   ASSERT_TRUE(scoped_libc_timezone.is_success());
 
+  if (IsMultiCalendarEnabled()) {
+    EXPECT_TRUE(calendar_list_model()->get_is_cached());
+    EXPECT_EQ(1u, calendar_list_model()->GetCachedCalendarList().size());
+  }
+
   // Set today to`kStartTime0`.
   SetTodayFromStr(kStartTime0);
 
@@ -608,10 +706,15 @@
 }
 
 // Test for pruning of events.
-TEST_F(CalendarModelTest, PruneEvents) {
+TEST_P(CalendarModelTest, PruneEvents) {
   // Sets the timezone to "GMT".
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
 
+  if (IsMultiCalendarEnabled()) {
+    EXPECT_TRUE(calendar_list_model()->get_is_cached());
+    EXPECT_EQ(1u, calendar_list_model()->GetCachedCalendarList().size());
+  }
+
   // The number of event is exactly the max cached capacity. No events should be
   // removed when init the mock response.
   constexpr int kNumEvents = calendar_utils::kMaxNumPrunableMonths +
@@ -681,10 +784,15 @@
   EXPECT_EQ((int)event_months().size(), kNumEvents + 1);
 }
 
-TEST_F(CalendarModelTest, RecordFetchResultHistogram_Success) {
+TEST_P(CalendarModelTest, RecordFetchResultHistogram_Success) {
   // Sets the timezone to "GMT".
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
 
+  if (IsMultiCalendarEnabled()) {
+    EXPECT_TRUE(calendar_list_model()->get_is_cached());
+    EXPECT_EQ(1u, calendar_list_model()->GetCachedCalendarList().size());
+  }
+
   base::HistogramTester histogram_tester;
 
   // Current date is just `kStartTime0`.
@@ -708,10 +816,15 @@
       /*expected_count=*/calendar_utils::kMaxNumNonPrunableMonths);
 }
 
-TEST_F(CalendarModelTest, RecordFetchResultHistogram_Failure) {
+TEST_P(CalendarModelTest, RecordFetchResultHistogram_Failure) {
   // Sets the timezone to "GMT".
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
 
+  if (IsMultiCalendarEnabled()) {
+    EXPECT_TRUE(calendar_list_model()->get_is_cached());
+    EXPECT_EQ(1u, calendar_list_model()->GetCachedCalendarList().size());
+  }
+
   base::HistogramTester histogram_tester;
 
   // Current date is just `kStartTime0`.
@@ -757,10 +870,15 @@
                                      /*expected_count=*/2);
 }
 
-TEST_F(CalendarModelTest, SessionStateChange) {
+TEST_P(CalendarModelTest, SessionStateChange) {
   // Sets the timezone to "GMT".
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
 
+  if (IsMultiCalendarEnabled()) {
+    EXPECT_TRUE(calendar_list_model()->get_is_cached());
+    EXPECT_EQ(1u, calendar_list_model()->GetCachedCalendarList().size());
+  }
+
   // Current date is just `kStartTime0`.
   SetTodayFromStr(kStartTime0);
 
@@ -794,10 +912,15 @@
   EXPECT_TRUE(events.empty());
 }
 
-TEST_F(CalendarModelTest, ActiveUserChange) {
+TEST_P(CalendarModelTest, ActiveUserChange) {
   // Sets the timezone to "GMT".
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
 
+  if (IsMultiCalendarEnabled()) {
+    EXPECT_TRUE(calendar_list_model()->get_is_cached());
+    EXPECT_EQ(1u, calendar_list_model()->GetCachedCalendarList().size());
+  }
+
   // Set up two users, user1 is the active user.
   UpdateSession(1u, "user1@test.com");
   UpdateSession(2u, "user2@test.com");
@@ -841,10 +964,15 @@
   EXPECT_TRUE(event_months().empty());
 }
 
-TEST_F(CalendarModelTest, ActiveChildUserChange) {
+TEST_P(CalendarModelTest, ActiveChildUserChange) {
   // Sets the timezone to "GMT".
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
 
+  if (IsMultiCalendarEnabled()) {
+    EXPECT_TRUE(calendar_list_model()->get_is_cached());
+    EXPECT_EQ(1u, calendar_list_model()->GetCachedCalendarList().size());
+  }
+
   // Set up two users, user1 is the active user.
   UpdateSession(1u, "user1@test.com", /*is_child*/ true);
   UpdateSession(2u, "user2@test.com", /*is_child*/ true);
@@ -888,7 +1016,7 @@
   EXPECT_TRUE(event_months().empty());
 }
 
-TEST_F(CalendarModelTest, ClearEvents) {
+TEST_P(CalendarModelTest, ClearEvents) {
   // Sets the timezone to "GMT".
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
 
@@ -963,7 +1091,7 @@
 
 // Test for filtering of events based on their statuses. Cancelled or declined
 // events shouldn't be inserted in a month.
-TEST_F(CalendarModelTest, ShouldFilterEvents) {
+TEST_P(CalendarModelTest, ShouldFilterEvents) {
   // Sets the timezone to "GMT".
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
 
@@ -1026,7 +1154,7 @@
                   "confirmed+needs_action", "confirmed+tentative"}));
 }
 
-TEST_F(CalendarModelTest, EdgeOfMonthEvent) {
+TEST_P(CalendarModelTest, EdgeOfMonthEvent) {
   // Will add event that's in the same month as kNow using PDT (UTC-7),
   // so the times will translate to next day (and month) on UTC.
   ash::system::ScopedTimezoneSettings timezone_settings(u"America/Los_Angeles");
@@ -1073,7 +1201,7 @@
   EXPECT_TRUE(next_month_map->second.empty());
 }
 
-TEST_F(CalendarModelTest, MultiDayEvents) {
+TEST_P(CalendarModelTest, MultiDayEvents) {
   // Set timezone and fake now.
   const char* kNow = "10 Nov 2022 13:00 GMT";
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
@@ -1147,7 +1275,7 @@
   TestMultiDayEvent(events, kMultiYearStartTime, kMultiYearEndTime);
 }
 
-TEST_F(CalendarModelTest, MultiAllDayEvents) {
+TEST_P(CalendarModelTest, MultiAllDayEvents) {
   // Set timezone and fake now. We set this to be GMT+n as we previously
   // had a bug where all day events overflowed into the day after they were set
   // to end for GMT+ timezones.
@@ -1196,10 +1324,15 @@
   EXPECT_EQ(0, EventsNumberOfDay(kMultiAllDayEventEndTime, &events));
 }
 
-TEST_F(CalendarModelTest, FindFetchingStatus) {
+TEST_P(CalendarModelTest, FindFetchingStatus) {
   // Sets the timezone to "GMT".
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
 
+  if (IsMultiCalendarEnabled()) {
+    EXPECT_TRUE(calendar_list_model()->get_is_cached());
+    EXPECT_EQ(1u, calendar_list_model()->GetCachedCalendarList().size());
+  }
+
   std::unique_ptr<google_apis::calendar::CalendarEvent> event0 =
       calendar_test_utils::CreateEvent(kId0, kSummary0, kStartTime0, kEndTime0);
   std::unique_ptr<google_apis::calendar::CalendarEvent> event1 =
@@ -1217,8 +1350,11 @@
 
   SetTodayFromStr(kStartTime0);
 
-  // Mock that events 0~4 has fetched.
-  MockOnEventsFetched(now(), google_apis::ApiErrorCode::HTTP_SUCCESS,
+  base::Time start_of_month0 = calendar_utils::GetStartOfMonthUTC(
+      calendar_test_utils::GetTimeFromString(kStartTime0));
+
+  // Mock that events 0~3 has fetched.
+  MockOnEventsFetched(start_of_month0, google_apis::ApiErrorCode::HTTP_SUCCESS,
                       event_list.get());
 
   // Starts fetching a date that is not in the cache.
@@ -1264,7 +1400,7 @@
                 calendar_utils::GetStartOfMonthUTC(fetching_date)));
 }
 
-TEST_F(CalendarModelTest, FindEventsSplitByMultiDayAndSameDay) {
+TEST_P(CalendarModelTest, FindEventsSplitByMultiDayAndSameDay) {
   // Set timezone and fake now.
   const char* kNow = "10 Nov 2022 13:00 GMT+5";
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT+5");
@@ -1306,7 +1442,7 @@
   EXPECT_EQ(same_day_events.back().id(), kSameDayId);
 }
 
-TEST_F(CalendarModelTest, FindUpcomingEvents_SameDay) {
+TEST_P(CalendarModelTest, FindUpcomingEvents_SameDay) {
   // Set timezone and fake now.
   const char* kNow = "10 Nov 2022 13:00 GMT";
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
@@ -1385,7 +1521,7 @@
 // we made the change to the logic of showing the up next view. Before the
 // change, we would show the events starting in 10 mins even if it's in the next
 // day. Now it shouldn't be shown.
-TEST_F(CalendarModelTest, FindUpcomingEvents_NextDay) {
+TEST_P(CalendarModelTest, FindUpcomingEvents_NextDay) {
   // Set timezone and fake now.
   const char* kNow = "10 Nov 2022 23:55 GMT";
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
@@ -1420,7 +1556,7 @@
 
 // If time now is 00:10 and we have an event that started <1 hour ago, then we
 // should get in progress events from the previous day back.
-TEST_F(CalendarModelTest, FindUpcomingEvents_PreviousDay) {
+TEST_P(CalendarModelTest, FindUpcomingEvents_PreviousDay) {
   // Set timezone and fake now.
   const char* kNow = "10 Nov 2022 00:10 GMT";
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
@@ -1455,7 +1591,7 @@
 
 // If the next event doesn't start in the next 10 mins, we'll still show it.
 // This is needed after we changed the logic of showing the up next view.
-TEST_F(CalendarModelTest, FindUpcomingEvents_ShowTheNextEvent) {
+TEST_P(CalendarModelTest, FindUpcomingEvents_ShowTheNextEvent) {
   // Set timezone and fake now.
   const char* kNow = "10 Nov 2022 13:00 GMT";
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
@@ -1493,7 +1629,7 @@
 // Returns:
 // First event: 13:00 - 13:45
 // Second event: 13:00 - 14:00
-TEST_F(CalendarModelTest, EventsSortingWithSameStartTime) {
+TEST_P(CalendarModelTest, EventsSortingWithSameStartTime) {
   // Set timezone and fake now.
   const char* kNow = "10 Nov 2022 13:00 GMT";
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
@@ -1531,7 +1667,7 @@
 }
 
 // Shows all events that start in 10 mins.
-TEST_F(CalendarModelTest, ShowEventsStartIn10MinsAsUpNext) {
+TEST_P(CalendarModelTest, ShowEventsStartIn10MinsAsUpNext) {
   // Set timezone and fake now.
   const char* kNow = "10 Nov 2022 13:00 GMT";
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
@@ -1572,7 +1708,7 @@
 }
 
 // Shows the first event if there's no events that start in 10 mins.
-TEST_F(CalendarModelTest, ShowTheFirstEventAsUpNext) {
+TEST_P(CalendarModelTest, ShowTheFirstEventAsUpNext) {
   // Set timezone and fake now.
   const char* kNow = "10 Nov 2022 13:00 GMT";
   ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
diff --git a/ash/system/time/calendar_month_view.cc b/ash/system/time/calendar_month_view.cc
index 9d3fd4a..23c38fc 100644
--- a/ash/system/time/calendar_month_view.cc
+++ b/ash/system/time/calendar_month_view.cc
@@ -438,10 +438,19 @@
 
   fetch_month_ = first_day_of_month_local.UTCMidnight();
 
-  // TODO(b/308701913): Set up calendar list model observer and modify event
-  // fetching logic accordingly.
+  if (calendar_utils::IsMultiCalendarEnabled()) {
+    // Set up the Calendar List Model observer to trigger an event fetch only
+    // after the calendar list fetch is completed.
+    scoped_calendar_list_model_observer_.Observe(calendar_list_model_.get());
 
-  FetchEvents(fetch_month_);
+    // If the month view has been created after a successful calendar list
+    // fetch, this will trigger an event list fetch immediately. Otherwise,
+    // events will be fetched during `OnCalendarListFetchComplete`.
+    calendar_model_->MaybeFetchEvents(fetch_month_);
+  } else {
+    FetchEvents(fetch_month_);
+  }
+
   bool has_fetched_data =
       calendar_view_controller_->IsSuccessfullyFetched(fetch_month_);
   const bool should_fetch_calendar_data =
@@ -633,13 +642,12 @@
 
 void CalendarMonthView::OnEventsFetched(
     const CalendarModel::FetchingStatus status,
-    const base::Time start_time,
-    const google_apis::calendar::EventList* events) {
+    const base::Time start_time) {
   if (status == CalendarModel::kSuccess && start_time == fetch_month_) {
     UpdateIsFetchedAndRepaint(true);
   }
 
-  if (!events || events->items().size() == 0) {
+  if (!(calendar_model_->MonthHasEvents(start_time))) {
     return;
   }
 
diff --git a/ash/system/time/calendar_month_view.h b/ash/system/time/calendar_month_view.h
index fa7f637c..9a18ec09 100644
--- a/ash/system/time/calendar_month_view.h
+++ b/ash/system/time/calendar_month_view.h
@@ -151,8 +151,7 @@
 
   // CalendarModel::Observer:
   void OnEventsFetched(const CalendarModel::FetchingStatus status,
-                       const base::Time start_time,
-                       const google_apis::calendar::EventList* events) override;
+                       const base::Time start_time) override;
 
   // Enable each cell's focus behavior.
   void EnableFocus();
diff --git a/ash/system/time/calendar_month_view_unittest.cc b/ash/system/time/calendar_month_view_unittest.cc
index 23f1fce2..a9d2765 100644
--- a/ash/system/time/calendar_month_view_unittest.cc
+++ b/ash/system/time/calendar_month_view_unittest.cc
@@ -12,12 +12,15 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/system/model/system_tray_model.h"
+#include "ash/system/time/calendar_list_model.h"
+#include "ash/system/time/calendar_model.h"
 #include "ash/system/time/calendar_unittest_utils.h"
 #include "ash/system/time/calendar_utils.h"
 #include "ash/system/time/calendar_view_controller.h"
 #include "ash/test/ash_test_base.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "base/time/time_override.h"
 #include "chromeos/ash/components/settings/scoped_timezone_settings.h"
@@ -31,6 +34,7 @@
 
 using ::google_apis::calendar::CalendarEvent;
 using ::google_apis::calendar::EventList;
+using ::google_apis::calendar::SingleCalendar;
 
 std::unique_ptr<google_apis::calendar::EventList> CreateMockEventList() {
   auto event_list = std::make_unique<google_apis::calendar::EventList>();
@@ -57,6 +61,12 @@
   return event_list;
 }
 
+const char* kCalendarId1 = "user1@email.com";
+const char* kCalendarSummary1 = "user1@email.com";
+const char* kCalendarColorId1 = "12";
+bool kCalendarSelected1 = true;
+bool kCalendarPrimary1 = true;
+
 }  // namespace
 
 class CalendarMonthViewTest : public AshTestBase {
@@ -318,10 +328,15 @@
                       ->GetText());
 }
 
-class CalendarMonthViewFetchTest : public AshTestBase {
+class CalendarMonthViewFetchTest
+    : public AshTestBase,
+      public testing::WithParamInterface</*multi_calendar_enabled=*/bool> {
  public:
   CalendarMonthViewFetchTest()
-      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
+    scoped_feature_list_.InitWithFeatureState(
+        ash::features::kMultiCalendarSupport, IsMultiCalendarEnabled());
+  }
   CalendarMonthViewFetchTest(const CalendarMonthViewFetchTest& other) = delete;
   CalendarMonthViewFetchTest& operator=(
       const CalendarMonthViewFetchTest& other) = delete;
@@ -335,6 +350,8 @@
     account_id_ = AccountId::FromUserEmail(email);
     Shell::Get()->calendar_controller()->SetActiveUserAccountIdForTesting(
         account_id_);
+    calendar_list_model_ =
+        Shell::Get()->system_tray_model()->calendar_list_model();
     calendar_model_ = Shell::Get()->system_tray_model()->calendar_model();
     calendar_client_ =
         std::make_unique<calendar_test_utils::CalendarClientTestImpl>();
@@ -345,16 +362,25 @@
         ash::prefs::kCalendarIntegrationEnabled, true);
     widget_ = CreateFramelessTestWidget();
     widget_->SetFullscreen(true);
+
+    if (IsMultiCalendarEnabled()) {
+      // Sets a mock calendar list so the calendar list fetch returns
+      // successfully.
+      SetCalendarList();
+    }
   }
 
   void TearDown() override {
     widget_.reset();
     time_overrides_.reset();
     controller_.reset();
+    scoped_feature_list_.Reset();
 
     AshTestBase::TearDown();
   }
 
+  bool IsMultiCalendarEnabled() { return GetParam(); }
+
   void CreateMonthView(base::Time date) {
     if (!widget_) {
       widget_ = CreateFramelessTestWidget();
@@ -373,6 +399,16 @@
 
   void DestroyCalendarMonthViewWidget() { widget_.reset(); }
 
+  void SetCalendarList() {
+    // Sets a mock calendar list.
+    std::list<std::unique_ptr<google_apis::calendar::SingleCalendar>> calendars;
+    calendars.push_back(calendar_test_utils::CreateCalendar(
+        kCalendarId1, kCalendarSummary1, kCalendarColorId1, kCalendarSelected1,
+        kCalendarPrimary1));
+    calendar_client_->SetCalendarList(
+        calendar_test_utils::CreateMockCalendarList(std::move(calendars)));
+  }
+
   int EventsNumberOfDay(const char* day, SingleDayEventList* events) {
     base::Time day_base = calendar_test_utils::GetTimeFromString(day);
 
@@ -424,17 +460,25 @@
         ->is_events_indicator_drawn;
   }
 
+  CalendarListModel* calendar_list_model() { return calendar_list_model_; }
+
   std::unique_ptr<base::subtle::ScopedTimeClockOverrides> time_overrides_;
 
   std::unique_ptr<views::Widget> widget_;
+  raw_ptr<CalendarListModel, DanglingUntriaged> calendar_list_model_;
   raw_ptr<CalendarModel, DanglingUntriaged> calendar_model_;
   std::unique_ptr<calendar_test_utils::CalendarClientTestImpl> calendar_client_;
   raw_ptr<CalendarMonthView, DanglingUntriaged> calendar_month_view_;
   std::unique_ptr<CalendarViewController> controller_;
   AccountId account_id_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-TEST_F(CalendarMonthViewFetchTest, FetchedBeforeMonthViewIsCreated) {
+INSTANTIATE_TEST_SUITE_P(MultiCalendar,
+                         CalendarMonthViewFetchTest,
+                         testing::Bool());
+
+TEST_P(CalendarMonthViewFetchTest, FetchedBeforeMonthViewIsCreated) {
   // Create a monthview based on Aug,1st 2021. Today is set to 18th.
   base::Time date;
   ASSERT_TRUE(base::Time::FromString("1 Aug 2021 10:00 GMT", &date));
@@ -447,6 +491,11 @@
   // Used to fetch events.
   base::Time month_start_midnight = calendar_utils::GetStartOfMonthUTC(today);
 
+  if (IsMultiCalendarEnabled()) {
+    calendar_list_model()->FetchCalendars();
+    WaitUntilFetched();
+  }
+
   // Sets the event list response and fetches the events.
   auto event_list = CreateMockEventList();
   SingleDayEventList events;
@@ -486,7 +535,7 @@
   EXPECT_TRUE(is_events_indicator_drawn(17));
 }
 
-TEST_F(CalendarMonthViewFetchTest, UpdateEvents) {
+TEST_P(CalendarMonthViewFetchTest, UpdateEvents) {
   // Create a monthview based on Aug,1st 2021. Today is set to 18th.
   base::Time date;
   ASSERT_TRUE(base::Time::FromString("1 Aug 2021 10:00 GMT", &date));
@@ -516,16 +565,23 @@
       static_cast<CalendarDateCellView*>(calendar_month_view_->children()[17])
           ->GetTooltipText());
 
-  // Sets the event list response and fetches the events.
+  // Sets the event list response.
   auto event_list = CreateMockEventList();
   SingleDayEventList events;
   EXPECT_EQ(0, EventsNumberOfDay(today, &events));
   EXPECT_TRUE(events.empty());
   SetEventList(std::move(event_list));
-  calendar_model_->FetchEvents(calendar_utils::GetStartOfMonthUTC(today));
 
-  // After events are fetched before the response is back the event number
-  // is not updated.
+  if (IsMultiCalendarEnabled()) {
+    // Start a calendar list fetch, after which we expect an event fetch to be
+    // triggered.
+    calendar_list_model()->FetchCalendars();
+  } else {
+    calendar_model_->FetchEvents(calendar_utils::GetStartOfMonthUTC(today));
+  }
+
+  // After the fetch is triggered, before the response is back, the event
+  // number is not updated.
   EXPECT_EQ(u"2", static_cast<CalendarDateCellView*>(
                       calendar_month_view_->children()[32])
                       ->GetText());
@@ -561,7 +617,7 @@
           ->GetTooltipText());
 }
 
-TEST_F(CalendarMonthViewFetchTest, RecordEventsDisplayedToUserOnce) {
+TEST_P(CalendarMonthViewFetchTest, RecordEventsDisplayedToUserOnce) {
   base::HistogramTester histogram_tester;
   // Create a monthview based on Aug,1st 2021. Today is set to 18th.
   base::Time date;
@@ -577,10 +633,17 @@
   // Nothing logged before we've fetched events.
   histogram_tester.ExpectTotalCount("Ash.Calendar.EventsDisplayedToUser", 0);
 
-  // Sets the event list response and fetches the events.
+  // Sets the event list response.
   auto event_list = CreateMockEventList();
   SetEventList(std::move(event_list));
-  calendar_model_->FetchEvents(calendar_utils::GetStartOfMonthUTC(today));
+
+  if (IsMultiCalendarEnabled()) {
+    // Start a calendar list fetch, after which we expect an event fetch to be
+    // triggered.
+    calendar_list_model()->FetchCalendars();
+  } else {
+    calendar_model_->FetchEvents(calendar_utils::GetStartOfMonthUTC(today));
+  }
   WaitUntilFetched();
 
   // After fetching, we expect the metric to be logged.
@@ -596,7 +659,7 @@
   histogram_tester.ExpectTotalCount("Ash.Calendar.EventsDisplayedToUser", 1);
 }
 
-TEST_F(CalendarMonthViewFetchTest, TimeZone) {
+TEST_P(CalendarMonthViewFetchTest, TimeZone) {
   // Create a monthview based on Aug,1st 2021. Today is set to 18th.
   base::Time date;
   ASSERT_TRUE(base::Time::FromString("1 Aug 2021 10:00 GMT", &date));
@@ -609,15 +672,20 @@
   CreateMonthView(date);
   WaitUntilPainted();
 
-  // Sets the event list response and fetches the events.
+  // Sets the event list response.
   auto event_list = CreateMockEventList();
   SingleDayEventList events;
   EXPECT_EQ(0, EventsNumberOfDay(today, &events));
   EXPECT_TRUE(events.empty());
   SetEventList(std::move(event_list));
-  calendar_model_->FetchEvents(calendar_utils::GetStartOfMonthUTC(today));
 
-  // Waits the fetch to be completed.
+  if (IsMultiCalendarEnabled()) {
+    // Start a calendar list fetch, after which we expect an event fetch to be
+    // triggered.
+    calendar_list_model()->FetchCalendars();
+  } else {
+    calendar_model_->FetchEvents(calendar_utils::GetStartOfMonthUTC(today));
+  }
   WaitUntilFetched();
 
   EXPECT_EQ(u"18", static_cast<CalendarDateCellView*>(
@@ -647,7 +715,7 @@
           ->GetTooltipText());
 }
 
-TEST_F(CalendarMonthViewFetchTest, InactiveUserSession) {
+TEST_P(CalendarMonthViewFetchTest, InactiveUserSession) {
   // Create a monthview based on Aug,1st 2021. Today is set to 18th.
   base::Time date;
   ASSERT_TRUE(base::Time::FromString("1 Aug 2021 10:00 GMT", &date));
@@ -660,15 +728,20 @@
   CreateMonthView(date);
   WaitUntilPainted();
 
-  // Sets the event list response and fetches the events.
+  // Sets the event list response.
   auto event_list = CreateMockEventList();
   SingleDayEventList events;
   EXPECT_EQ(0, EventsNumberOfDay(today, &events));
   EXPECT_TRUE(events.empty());
   SetEventList(std::move(event_list));
-  calendar_model_->FetchEvents(calendar_utils::GetStartOfMonthUTC(today));
 
-  // Waits the fetch to be completed.
+  if (IsMultiCalendarEnabled()) {
+    // Start a calendar list fetch, after which we expect an event fetch to be
+    // triggered.
+    calendar_list_model()->FetchCalendars();
+  } else {
+    calendar_model_->FetchEvents(calendar_utils::GetStartOfMonthUTC(today));
+  }
   WaitUntilFetched();
 
   EXPECT_EQ(u"18", static_cast<CalendarDateCellView*>(
diff --git a/ash/system/time/calendar_unittest_utils.cc b/ash/system/time/calendar_unittest_utils.cc
index 37a077d2..5a8c657 100644
--- a/ash/system/time/calendar_unittest_utils.cc
+++ b/ash/system/time/calendar_unittest_utils.cc
@@ -192,8 +192,11 @@
     const base::Time end_time,
     const std::string& calendar_id,
     const std::string& calendar_color_id) {
-  // TODO(b/308696020): Implement Test Client changes in conjunction with
-  // Calendar Model changes.
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(std::move(callback), error_, std::move(events_)),
+      task_delay_);
+
   return base::DoNothing();
 }
 
diff --git a/ash/system/time/calendar_up_next_pixeltest.cc b/ash/system/time/calendar_up_next_pixeltest.cc
index 4b7d5ff..a690c97 100644
--- a/ash/system/time/calendar_up_next_pixeltest.cc
+++ b/ash/system/time/calendar_up_next_pixeltest.cc
@@ -16,6 +16,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "chromeos/ash/components/settings/scoped_timezone_settings.h"
+#include "google_apis/calendar/calendar_api_requests.h"
 #include "google_apis/calendar/calendar_api_response_types.h"
 
 namespace ash {
@@ -77,6 +78,7 @@
     Shell::Get()->system_tray_model()->calendar_model()->OnEventsFetched(
         calendar_utils::GetStartOfMonthUTC(
             base::subtle::TimeNowIgnoringOverride().LocalMidnight()),
+        google_apis::calendar::kPrimaryCalendarId,
         google_apis::ApiErrorCode::HTTP_SUCCESS,
         calendar_test_utils::CreateMockEventList(std::move(events)).get());
 
diff --git a/ash/system/time/calendar_up_next_view_unittest.cc b/ash/system/time/calendar_up_next_view_unittest.cc
index 56cedf39..f6ecd3e 100644
--- a/ash/system/time/calendar_up_next_view_unittest.cc
+++ b/ash/system/time/calendar_up_next_view_unittest.cc
@@ -18,6 +18,7 @@
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/time/time.h"
+#include "google_apis/calendar/calendar_api_requests.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/scroll_view.h"
 
@@ -89,6 +90,7 @@
     Shell::Get()->system_tray_model()->calendar_model()->OnEventsFetched(
         calendar_utils::GetStartOfMonthUTC(
             base::subtle::TimeNowIgnoringOverride().LocalMidnight()),
+        google_apis::calendar::kPrimaryCalendarId,
         google_apis::ApiErrorCode::HTTP_SUCCESS,
         calendar_test_utils::CreateMockEventList(std::move(events)).get());
 
diff --git a/ash/system/time/calendar_view.cc b/ash/system/time/calendar_view.cc
index 8146b8d..6d324b2 100644
--- a/ash/system/time/calendar_view.cc
+++ b/ash/system/time/calendar_view.cc
@@ -571,7 +571,9 @@
   content_view_->SetPaintToLayer();
   content_view_->layer()->SetFillsBoundsOpaquely(false);
 
-  // TODO(b/308701913): Initiate calendar list fetch here.
+  if (calendar_utils::IsMultiCalendarEnabled()) {
+    calendar_list_model_->FetchCalendars();
+  }
 
   SetMonthViews();
 
@@ -966,8 +968,13 @@
 }
 
 void CalendarView::MaybeUpdateLoadingBarVisibility() {
-  // TODO(b/308701913): Check for calendar list fetch completion as well here.
-  const bool visible = !EventsFetchComplete();
+  bool visible;
+  if (calendar_utils::IsMultiCalendarEnabled()) {
+    visible = !(!calendar_list_model_->get_fetch_in_progress() &&
+                EventsFetchComplete());
+  } else {
+    visible = !EventsFetchComplete();
+  }
   progress_bar_->UpdateProgressBarVisibility(
       /*visible=*/visible);
 
@@ -1247,10 +1254,8 @@
       .SetOpacity(temp_header_, 1.0f);
 }
 
-void CalendarView::OnEventsFetched(
-    const CalendarModel::FetchingStatus status,
-    const base::Time start_time,
-    const google_apis::calendar::EventList* events) {
+void CalendarView::OnEventsFetched(const CalendarModel::FetchingStatus status,
+                                   const base::Time start_time) {
   if (base::Contains(on_screen_month_, start_time)) {
     on_screen_month_[start_time] = status;
   }
@@ -1266,14 +1271,6 @@
   }
 }
 
-void CalendarView::OnTimeout(const base::Time start_time) {
-  if (base::Contains(on_screen_month_, start_time)) {
-    on_screen_month_[start_time] = CalendarModel::kNever;
-  }
-
-  MaybeUpdateLoadingBarVisibility();
-}
-
 void CalendarView::OpenEventList() {
   // Don't show the the `event_list_` view for unlogged in users.
   if (!calendar_utils::ShouldFetchCalendarData()) {
diff --git a/ash/system/time/calendar_view.h b/ash/system/time/calendar_view.h
index 65a8c1a..10b821e 100644
--- a/ash/system/time/calendar_view.h
+++ b/ash/system/time/calendar_view.h
@@ -86,9 +86,7 @@
 
   // CalendarModel::Observer:
   void OnEventsFetched(const CalendarModel::FetchingStatus status,
-                       const base::Time start_time,
-                       const google_apis::calendar::EventList* events) override;
-  void OnTimeout(base::Time start_of_month) override;
+                       const base::Time start_time) override;
 
   // CalendarViewController::Observer:
   void OnMonthChanged() override;
@@ -276,6 +274,9 @@
   // Updates the on-screen month map with the current months on screen.
   void UpdateOnScreenMonthMap();
 
+  // Returns true if there is no Calendar List fetch in progress.
+  bool CalendarsFetchComplete();
+
   // Returns whether or not we've finished fetching CalendarEvents.
   bool EventsFetchComplete();
 
diff --git a/ash/system/time/calendar_view_controller.h b/ash/system/time/calendar_view_controller.h
index 66a38a7..05568eef 100644
--- a/ash/system/time/calendar_view_controller.h
+++ b/ash/system/time/calendar_view_controller.h
@@ -210,6 +210,7 @@
   // For unit tests.
   friend class CalendarMonthViewTest;
   friend class CalendarViewAnimationTest;
+  friend class CalendarViewEventListViewFetchTest;
   friend class CalendarViewEventListViewTest;
   friend class CalendarViewTest;
   friend class CalendarViewEventListItemViewTest;
diff --git a/ash/system/time/calendar_view_pixeltest.cc b/ash/system/time/calendar_view_pixeltest.cc
index beff002..f0c0b7f 100644
--- a/ash/system/time/calendar_view_pixeltest.cc
+++ b/ash/system/time/calendar_view_pixeltest.cc
@@ -18,6 +18,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "components/account_id/account_id.h"
+#include "google_apis/calendar/calendar_api_requests.h"
 #include "google_apis/calendar/calendar_api_response_types.h"
 
 namespace ash {
@@ -96,6 +97,7 @@
     Shell::Get()->system_tray_model()->calendar_model()->OnEventsFetched(
         calendar_utils::GetStartOfMonthUTC(
             base::subtle::TimeNowIgnoringOverride().LocalMidnight()),
+        google_apis::calendar::kPrimaryCalendarId,
         google_apis::ApiErrorCode::HTTP_SUCCESS,
         calendar_test_utils::CreateMockEventList(std::move(events)).get());
   }
diff --git a/ash/system/time/calendar_view_unittest.cc b/ash/system/time/calendar_view_unittest.cc
index b74b20e..07c09d2 100644
--- a/ash/system/time/calendar_view_unittest.cc
+++ b/ash/system/time/calendar_view_unittest.cc
@@ -17,6 +17,7 @@
 #include "ash/style/icon_button.h"
 #include "ash/system/notification_center/views/notification_center_view.h"
 #include "ash/system/time/calendar_event_list_view.h"
+#include "ash/system/time/calendar_list_model.h"
 #include "ash/system/time/calendar_model.h"
 #include "ash/system/time/calendar_month_view.h"
 #include "ash/system/time/calendar_unittest_utils.h"
@@ -32,12 +33,14 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "base/time/time_override.h"
 #include "base/types/cxx23_to_underlying.h"
 #include "chromeos/ash/components/settings/scoped_timezone_settings.h"
 #include "components/account_id/account_id.h"
+#include "google_apis/calendar/calendar_api_requests.h"
 #include "google_apis/common/api_error_codes.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/compositor/layer.h"
@@ -57,9 +60,16 @@
 
 using ::google_apis::calendar::CalendarEvent;
 using ::google_apis::calendar::EventList;
+using ::google_apis::calendar::SingleCalendar;
 
 constexpr char kTestUser[] = "user@test";
 
+const char* kCalendarId1 = "user1@email.com";
+const char* kCalendarSummary1 = "user1@email.com";
+const char* kCalendarColorId1 = "12";
+bool kCalendarSelected1 = true;
+bool kCalendarPrimary1 = true;
+
 }  // namespace
 
 class CalendarViewControllerTestObserver
@@ -1397,10 +1407,15 @@
 // A test class for testing animation. This class cannot set fake now since it's
 // using `MOCK_TIME` to test the animations, and it can't inherit from
 // CalendarAnimationTest due to the same reason.
-class CalendarViewAnimationTest : public AshTestBase {
+class CalendarViewAnimationTest
+    : public AshTestBase,
+      public testing::WithParamInterface</*multi_calendar_enabled=*/bool> {
  public:
   CalendarViewAnimationTest()
-      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
+    scoped_feature_list_.InitWithFeatureState(
+        ash::features::kMultiCalendarSupport, IsMultiCalendarEnabled());
+  }
   CalendarViewAnimationTest(const CalendarViewAnimationTest&) = delete;
   CalendarViewAnimationTest& operator=(const CalendarViewAnimationTest&) =
       delete;
@@ -1417,25 +1432,46 @@
     AccountId account_id = AccountId::FromUserEmail(email);
     Shell::Get()->calendar_controller()->SetActiveUserAccountIdForTesting(
         account_id);
+    calendar_list_model_ =
+        Shell::Get()->system_tray_model()->calendar_list_model();
     calendar_model_ = Shell::Get()->system_tray_model()->calendar_model();
     calendar_client_ =
         std::make_unique<calendar_test_utils::CalendarClientTestImpl>();
     Shell::Get()->calendar_controller()->RegisterClientForUser(
         account_id, calendar_client_.get());
+
+    if (IsMultiCalendarEnabled()) {
+      // Set a mock calendar list so the calendar list fetch returns
+      // successfully.
+      SetCalendarList();
+    }
   }
 
   void TearDown() override {
     widget_.reset();
     time_overrides_.reset();
+    scoped_feature_list_.Reset();
 
     AshTestBase::TearDown();
   }
 
+  bool IsMultiCalendarEnabled() { return GetParam(); }
+
   void CreateCalendarView() {
     calendar_view_ = widget_->SetContentsView(std::make_unique<CalendarView>(
         /*use_glanceables_container_style=*/false));
   }
 
+  void SetCalendarList() {
+    // Set a mock calendar list.
+    std::list<std::unique_ptr<google_apis::calendar::SingleCalendar>> calendars;
+    calendars.push_back(calendar_test_utils::CreateCalendar(
+        kCalendarId1, kCalendarSummary1, kCalendarColorId1, kCalendarSelected1,
+        kCalendarPrimary1));
+    calendar_client_->SetCalendarList(
+        calendar_test_utils::CreateMockCalendarList(std::move(calendars)));
+  }
+
   // Gets date cell of a given CalendarMonthView and numerical `day`.
   const views::LabelButton* GetDateCell(CalendarMonthView* month,
                                         std::u16string day) {
@@ -1531,6 +1567,7 @@
   views::View* next_label() { return calendar_view_->next_label_; }
   views::ScrollView* scroll_view() { return calendar_view_->scroll_view_; }
   views::View* event_list_view() { return calendar_view_->event_list_view_; }
+  CalendarListModel* calendar_list_model() { return calendar_list_model_; }
   CalendarModel* calendar_model() { return calendar_model_; }
   views::View* calendar_sliding_surface_view() {
     return calendar_view_->calendar_sliding_surface_;
@@ -1569,13 +1606,19 @@
   // Owned by `widget_`.
   raw_ptr<CalendarView, DanglingUntriaged> calendar_view_ = nullptr;
   std::unique_ptr<base::subtle::ScopedTimeClockOverrides> time_overrides_;
+  raw_ptr<CalendarListModel, DanglingUntriaged> calendar_list_model_;
   raw_ptr<CalendarModel, DanglingUntriaged> calendar_model_;
   std::unique_ptr<calendar_test_utils::CalendarClientTestImpl> calendar_client_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
+INSTANTIATE_TEST_SUITE_P(MultiCalendar,
+                         CalendarViewAnimationTest,
+                         testing::Bool());
+
 // The header should show the new header with animation once there's an update
 // when the event list view is shown.
-TEST_F(CalendarViewAnimationTest, HeaderAnimation) {
+TEST_P(CalendarViewAnimationTest, HeaderAnimation) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
   base::Time date;
@@ -1646,7 +1689,7 @@
   EXPECT_EQ(u"2021", header_year()->GetText());
 }
 
-TEST_F(CalendarViewAnimationTest, HeaderAnimationDirection) {
+TEST_P(CalendarViewAnimationTest, HeaderAnimationDirection) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
   base::Time date;
@@ -1697,7 +1740,7 @@
 }
 
 // The month views and header should animate when scrolling up or down.
-TEST_F(CalendarViewAnimationTest, MonthAndHeaderAnimation) {
+TEST_P(CalendarViewAnimationTest, MonthAndHeaderAnimation) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
 
@@ -1807,7 +1850,7 @@
 }
 
 // The content view should not be scrollable when the month view is animating.
-TEST_F(CalendarViewAnimationTest, NotScrollableWhenAnimating) {
+TEST_P(CalendarViewAnimationTest, NotScrollableWhenAnimating) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
 
@@ -1885,7 +1928,7 @@
   EXPECT_EQ(u"2021", header_year()->GetText());
 }
 
-TEST_F(CalendarViewAnimationTest, ResetToTodayWithAnimation) {
+TEST_P(CalendarViewAnimationTest, ResetToTodayWithAnimation) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
 
@@ -1982,7 +2025,7 @@
 // Tests that the loading bar becomes visible when any of the on screen months
 // has not finished fetching and becomes invisible once all months on screen
 // have finished fetching events.
-TEST_F(CalendarViewAnimationTest, LoadingBarVisibilityForOneMonthOnScreen) {
+TEST_P(CalendarViewAnimationTest, LoadingBarVisibilityForOneMonthOnScreen) {
   // Sets the timezone to "America/Los_Angeles".
   ash::system::ScopedTimezoneSettings timezone_settings(u"America/Los_Angeles");
 
@@ -2005,7 +2048,7 @@
   EXPECT_FALSE(progress_bar->GetVisible());
 }
 
-TEST_F(CalendarViewAnimationTest, LoadingBarVisibility) {
+TEST_P(CalendarViewAnimationTest, LoadingBarVisibility) {
   // Sets the timezone to "America/Los_Angeles".
   ash::system::ScopedTimezoneSettings timezone_settings(u"America/Los_Angeles");
 
@@ -2038,7 +2081,7 @@
 }
 
 // Tests the loading bar visibility for different user sessions.
-TEST_F(CalendarViewAnimationTest,
+TEST_P(CalendarViewAnimationTest,
        LoadingBarVisibilityForDifferentUserSessions) {
   // Make sure that the `CalendarView` can have enough space to hold at least 1
   // month. 600 should be enough since the calendar bubble's height is set to
@@ -2084,7 +2127,7 @@
 }
 
 // Tests the loading bar visibility for when fetching events errors.
-TEST_F(CalendarViewAnimationTest, LoadingBarVisibilityForErrorFetchingEvents) {
+TEST_P(CalendarViewAnimationTest, LoadingBarVisibilityForErrorFetchingEvents) {
   base::Time date;
   ASSERT_TRUE(base::Time::FromString("04 May 2022 15:00 GMT", &date));
 
@@ -2125,7 +2168,7 @@
 }
 
 // Tests the loading bar visibility for when fetching events times out.
-TEST_F(CalendarViewAnimationTest,
+TEST_P(CalendarViewAnimationTest,
        LoadingBarVisibilityForTimeoutFetchingEvents) {
   base::Time date;
   ASSERT_TRUE(base::Time::FromString("04 May 2022 15:00 GMT", &date));
@@ -2167,7 +2210,7 @@
 }
 
 // Tests that the EventListView does not crash if shown during the initial open.
-TEST_F(CalendarViewAnimationTest, QuickShowEventListInitialOpen) {
+TEST_P(CalendarViewAnimationTest, QuickShowEventListInitialOpen) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
 
@@ -2189,7 +2232,7 @@
 }
 
 // Tests that the EventListView does not show during the month change animation.
-TEST_F(CalendarViewAnimationTest, DontShowEventListDuringMonthAnimation) {
+TEST_P(CalendarViewAnimationTest, DontShowEventListDuringMonthAnimation) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
 
@@ -2215,7 +2258,7 @@
 // Tests open/close the `CalendarEventListView`. Also tests one corner case:
 // when closing the event list right after opening it, do nothing since the
 // animation is not finished.
-TEST_F(CalendarViewAnimationTest, OpenAndCloseEventList) {
+TEST_P(CalendarViewAnimationTest, OpenAndCloseEventList) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
   // Sets the timezone to "America/Los_Angeles".
@@ -2326,6 +2369,7 @@
       std::unique_ptr<google_apis::calendar::EventList> event_list) {
     Shell::Get()->system_tray_model()->calendar_model()->OnEventsFetched(
         calendar_utils::GetStartOfMonthUTC(date),
+        google_apis::calendar::kPrimaryCalendarId,
         google_apis::ApiErrorCode::HTTP_SUCCESS, event_list.get());
   }
 };
@@ -2765,6 +2809,8 @@
 
   // Clicks the reset to today button and `up_next_view()` should be visible.
   GestureTapOn(reset_to_today_button());
+  MockEventsFetched(calendar_utils::GetStartOfMonthUTC(date),
+                    CreateMockEventListWithEventStartTimeTenMinsAway());
   EXPECT_TRUE(up_next_view()->GetVisible());
 }
 
@@ -2988,19 +3034,19 @@
       &CalendarViewTest::FakeTimeNow, /*time_ticks_override=*/nullptr,
       /*thread_ticks_override=*/nullptr);
 
-  // First populate model with events.
-  MockEventsFetched(calendar_utils::GetStartOfMonthUTC(date),
-                    CreateMockEventListWithEventStartTimeTenMinsAway());
-  // Then build the Calendar view.
+  // Build the Calendar view.
   CreateCalendarView();
 
-  WaitForCalendarToCompleteLoading();
+  // Populate model with events.
+  MockEventsFetched(calendar_utils::GetStartOfMonthUTC(date),
+                    CreateMockEventListWithEventStartTimeTenMinsAway());
 
   // Up next should be displayed (with cached events).
   EXPECT_TRUE(up_next_view());
   EXPECT_EQ(size_t(1), up_next_scroll_contents()->children().size());
 
   // Replace the cached data with new data.
+  Shell::Get()->system_tray_model()->calendar_model()->ClearAllCachedEvents();
   MockEventsFetched(calendar_utils::GetStartOfMonthUTC(date),
                     CreateMockEventListWithTwoEventsOneEndingInOneMin());
   EXPECT_TRUE(up_next_view());
@@ -3034,11 +3080,30 @@
       std::unique_ptr<google_apis::calendar::EventList> event_list) {
     Shell::Get()->system_tray_model()->calendar_model()->OnEventsFetched(
         calendar_utils::GetStartOfMonthUTC(date),
+        google_apis::calendar::kPrimaryCalendarId,
         google_apis::ApiErrorCode::HTTP_SUCCESS, event_list.get());
   }
+
+  void MockOnCalendarListFetched() {
+    std::list<std::unique_ptr<google_apis::calendar::SingleCalendar>> calendars;
+    calendars.push_back(calendar_test_utils::CreateCalendar(
+        kCalendarId1, kCalendarSummary1, kCalendarColorId1, kCalendarSelected1,
+        kCalendarPrimary1));
+    std::unique_ptr<google_apis::calendar::CalendarList> calendar_list =
+        calendar_test_utils::CreateMockCalendarList(std::move(calendars));
+    Shell::Get()
+        ->system_tray_model()
+        ->calendar_list_model()
+        ->OnCalendarListFetched(google_apis::ApiErrorCode::HTTP_SUCCESS,
+                                std::move(calendar_list));
+  }
 };
 
-TEST_F(CalendarViewWithUpNextViewAnimationTest,
+INSTANTIATE_TEST_SUITE_P(MultiCalendar,
+                         CalendarViewWithUpNextViewAnimationTest,
+                         testing::Bool());
+
+TEST_P(CalendarViewWithUpNextViewAnimationTest,
        UpNextViewShouldNotCoversToday) {
   auto histogram_tester = std::make_unique<base::HistogramTester>();
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
@@ -3087,7 +3152,7 @@
   EXPECT_TRUE(todays_date_cell_is_visible);
 }
 
-TEST_F(CalendarViewWithUpNextViewAnimationTest,
+TEST_P(CalendarViewWithUpNextViewAnimationTest,
        ShouldNotScrollToShowTodaysCell_WhenUpNextViewDoesNotCoverIt) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
@@ -3124,7 +3189,7 @@
   EXPECT_TRUE(todays_date_cell_is_visible);
 }
 
-TEST_F(
+TEST_P(
     CalendarViewWithUpNextViewAnimationTest,
     ShouldNotScrollToShowTodaysCell_WhenUserHasScrolled_AndAnUpcomingEventAppears) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
@@ -3139,6 +3204,11 @@
   widget()->SetFullscreen(false);
   widget()->SetSize(gfx::Size(kTrayMenuWidth, 350));
 
+  if (IsMultiCalendarEnabled()) {
+    MockOnCalendarListFetched();
+    WaitUntilFetched();
+  }
+
   // Fetch an event that starts in 11 mins and up next will show.
   MockEventsFetched(calendar_utils::GetStartOfMonthUTC(date),
                     CreateUpcomingEvents(date + base::Minutes(6)));
@@ -3162,7 +3232,7 @@
   EXPECT_EQ(initial_scroll_position, scroll_view()->GetVisibleRect().y());
 }
 
-TEST_F(CalendarViewWithUpNextViewAnimationTest,
+TEST_P(CalendarViewWithUpNextViewAnimationTest,
        ShouldNotScroll_WhenAnUpcomingEventAppears) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
@@ -3198,7 +3268,7 @@
   EXPECT_EQ(initial_scroll_position, scroll_view()->GetVisibleRect().y());
 }
 
-TEST_F(CalendarViewWithUpNextViewAnimationTest,
+TEST_P(CalendarViewWithUpNextViewAnimationTest,
        ShouldNotScrollToShowTodaysCell_WhenTodaysDateCellIsNull) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
@@ -3212,6 +3282,11 @@
   widget()->SetFullscreen(false);
   widget()->SetSize(gfx::Size(kTrayMenuWidth, 350));
 
+  if (IsMultiCalendarEnabled()) {
+    MockOnCalendarListFetched();
+    WaitUntilFetched();
+  }
+
   // Fetch an event that starts in 11 mins and up next view will be shown.
   MockEventsFetched(calendar_utils::GetStartOfMonthUTC(date),
                     CreateUpcomingEvents(date + base::Minutes(6)));
@@ -3239,7 +3314,7 @@
 
 // Tests that the scroll view scrolls up to today's row without the up-next
 // view.
-TEST_F(CalendarViewWithUpNextViewAnimationTest,
+TEST_P(CalendarViewWithUpNextViewAnimationTest,
        ShouldScrollToToday_WithoutUpNextView) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
@@ -3257,7 +3332,7 @@
 }
 
 // Tests that the scroll view scrolls up to today's row with the up-next view.
-TEST_F(CalendarViewWithUpNextViewAnimationTest,
+TEST_P(CalendarViewWithUpNextViewAnimationTest,
        ShouldScrollToToday_WithUpNextView) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
diff --git a/ash/system/unified/unified_slider_bubble_controller.cc b/ash/system/unified/unified_slider_bubble_controller.cc
index 57fdf85..3ff8f607 100644
--- a/ash/system/unified/unified_slider_bubble_controller.cc
+++ b/ash/system/unified/unified_slider_bubble_controller.cc
@@ -101,6 +101,12 @@
   autoclose_.Stop();
   slider_controller_.reset();
   if (bubble_widget_) {
+    // Reset `slider_view_`
+    // to prevent dangling pointer caused by view removal.
+    // TODO(b/40280409): We shouldn't need this if child view removal is made
+    // more safe.
+    slider_view_ = nullptr;
+
     bubble_widget_->CloseNow();
   }
 }
@@ -129,6 +135,7 @@
 
 void UnifiedSliderBubbleController::BubbleViewDestroyed() {
   slider_controller_.reset();
+  slider_view_ = nullptr;
   bubble_view_ = nullptr;
   bubble_widget_ = nullptr;
 }
@@ -278,6 +285,11 @@
     CHECK(bubble_view_);
 
     if (slider_type_ != slider_type) {
+      // `RemoveAllChildViews` will cause `slider_view_` to be dangling, so we
+      // need to safely extract it.
+      // TODO(b/40280409): We shouldn't need this if child view removal is made
+      // more safe.
+      slider_view_ = nullptr;
       bubble_view_->RemoveAllChildViews();
 
       slider_type_ = slider_type;
diff --git a/ash/system/unified/unified_slider_bubble_controller.h b/ash/system/unified/unified_slider_bubble_controller.h
index cdcd8ff..d588a3c 100644
--- a/ash/system/unified/unified_slider_bubble_controller.h
+++ b/ash/system/unified/unified_slider_bubble_controller.h
@@ -106,7 +106,7 @@
 
   raw_ptr<TrayBubbleView> bubble_view_ = nullptr;
   raw_ptr<views::Widget> bubble_widget_ = nullptr;
-  raw_ptr<UnifiedSliderView, DanglingUntriaged> slider_view_ = nullptr;
+  raw_ptr<UnifiedSliderView> slider_view_ = nullptr;
 
   // Type of the currently shown slider.
   SliderType slider_type_ = SLIDER_TYPE_VOLUME;
diff --git a/ash/system/unified/unified_slider_bubble_controller_unittest.cc b/ash/system/unified/unified_slider_bubble_controller_unittest.cc
new file mode 100644
index 0000000..2082553
--- /dev/null
+++ b/ash/system/unified/unified_slider_bubble_controller_unittest.cc
@@ -0,0 +1,51 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/unified/unified_slider_bubble_controller.h"
+
+#include <vector>
+
+#include "ash/shelf/shelf.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/unified/unified_system_tray.h"
+#include "ash/test/ash_test_base.h"
+#include "base/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+namespace {
+
+using SliderType = UnifiedSliderBubbleController::SliderType;
+using UnifiedSliderBubbleControllerTest = AshTestBase;
+
+// Tests the bubble and slider view lifetime when opening and closing each type
+// of bubble.
+TEST_F(UnifiedSliderBubbleControllerTest, ShowAndHideBubble) {
+  std::vector<SliderType> types = {
+      SliderType::SLIDER_TYPE_VOLUME,
+      SliderType::SLIDER_TYPE_DISPLAY_BRIGHTNESS,
+      SliderType::SLIDER_TYPE_KEYBOARD_BACKLIGHT_TOGGLE_OFF,
+      SliderType::SLIDER_TYPE_KEYBOARD_BACKLIGHT_TOGGLE_ON,
+      SliderType::SLIDER_TYPE_KEYBOARD_BRIGHTNESS,
+      SliderType::SLIDER_TYPE_MIC};
+
+  UnifiedSliderBubbleController controller(
+      GetPrimaryShelf()->GetStatusAreaWidget()->unified_system_tray());
+
+  for (auto type : types) {
+    controller.ShowBubble(type);
+
+    EXPECT_TRUE(controller.IsBubbleShown());
+    EXPECT_TRUE(controller.slider_view());
+
+    controller.CloseBubble();
+    base::RunLoop().RunUntilIdle();
+
+    EXPECT_FALSE(controller.IsBubbleShown());
+    EXPECT_FALSE(controller.slider_view());
+  }
+}
+
+}  // namespace
+}  // namespace ash
diff --git a/ash/wallpaper/sea_pen_wallpaper_manager.cc b/ash/wallpaper/sea_pen_wallpaper_manager.cc
index 430a574..1f44bfdc 100644
--- a/ash/wallpaper/sea_pen_wallpaper_manager.cc
+++ b/ash/wallpaper/sea_pen_wallpaper_manager.cc
@@ -235,7 +235,6 @@
     const SeaPenImage& sea_pen_image,
     const personalization_app::mojom::SeaPenQueryPtr& query,
     SaveSeaPenImageCallback callback) {
-  CHECK(account_id.HasAccountIdKey());
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   image_util::DecodeImageData(
       base::BindOnce(&SeaPenWallpaperManager::OnSeaPenImageDecoded,
@@ -274,6 +273,18 @@
                                 GetFilePathForImageId(account_id, image_id)));
 }
 
+void SeaPenWallpaperManager::GetTemplateIdFromFile(
+    const AccountId& account_id,
+    const uint32_t image_id,
+    GetTemplateIdFromFileCallback callback) {
+  blocking_task_runner_->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&GetStringContent,
+                     GetFilePathForImageId(account_id, image_id)),
+      base::BindOnce(&SeaPenWallpaperManager::OnFileReadGetTemplateId,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
 void SeaPenWallpaperManager::GetImageAndMetadata(
     const AccountId& account_id,
     const uint32_t image_id,
@@ -438,4 +449,18 @@
       data_decoder::mojom::ImageCodec::kDefault, data);
 }
 
+void SeaPenWallpaperManager::OnFileReadGetTemplateId(
+    GetTemplateIdFromFileCallback callback,
+    const std::string& data) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (data.empty()) {
+    LOG(WARNING) << "Unable to read file";
+    std::move(callback).Run(std::nullopt);
+    return;
+  }
+
+  DecodeJsonMetadataGetTemplateId(ExtractDcDescriptionContents(data),
+                                  std::move(callback));
+}
+
 }  // namespace ash
diff --git a/ash/wallpaper/sea_pen_wallpaper_manager.h b/ash/wallpaper/sea_pen_wallpaper_manager.h
index 8b41de9..fcd7098 100644
--- a/ash/wallpaper/sea_pen_wallpaper_manager.h
+++ b/ash/wallpaper/sea_pen_wallpaper_manager.h
@@ -101,6 +101,16 @@
   // back of the automatic deletion queue.
   void TouchFile(const AccountId& account_id, uint32_t image_id);
 
+  using GetTemplateIdFromFileCallback =
+      base::OnceCallback<void(std::optional<int> template_id)>;
+
+  // Retrieves the template id from the Sea Pen image saved on disk at
+  // `image_id`. Calls callback with nullopt if the `image_id` does
+  // not exist, or errors reading the file or decoding the data.
+  void GetTemplateIdFromFile(const AccountId& account_id,
+                             const uint32_t image_id,
+                             GetTemplateIdFromFileCallback callback);
+
   using GetImageAndMetadataCallback = base::OnceCallback<void(
       const gfx::ImageSkia& image,
       personalization_app::mojom::RecentSeaPenImageInfoPtr image_info)>;
@@ -158,6 +168,9 @@
 
   void OnFileRead(GetImageAndMetadataCallback callback, std::string data);
 
+  void OnFileReadGetTemplateId(GetTemplateIdFromFileCallback callback,
+                               const std::string& data);
+
   std::unique_ptr<SessionDelegate> session_delegate_;
 
   scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
diff --git a/ash/wallpaper/sea_pen_wallpaper_manager_unittest.cc b/ash/wallpaper/sea_pen_wallpaper_manager_unittest.cc
index 49deaca4..4e8ab57 100644
--- a/ash/wallpaper/sea_pen_wallpaper_manager_unittest.cc
+++ b/ash/wallpaper/sea_pen_wallpaper_manager_unittest.cc
@@ -503,6 +503,47 @@
   }
 }
 
+TEST_F(SeaPenWallpaperManagerTest, GetTemplateIdFromFileSuccess) {
+  const uint32_t image_id = 88888888;
+
+  {
+    base::test::TestFuture<bool> save_image_future;
+    sea_pen_wallpaper_manager()->SaveSeaPenImage(
+        kAccountId1, {CreateJpgBytes(), image_id}, MakeTemplateQuery(),
+        save_image_future.GetCallback());
+    ASSERT_TRUE(save_image_future.Get());
+  }
+
+  {
+    base::test::TestFuture<std::optional<int>> get_template_id_future;
+    sea_pen_wallpaper_manager()->GetTemplateIdFromFile(
+        kAccountId1, image_id, get_template_id_future.GetCallback());
+    EXPECT_EQ(static_cast<int>(
+                  ash::personalization_app::mojom::SeaPenTemplateId::kFlower),
+              *get_template_id_future.Get());
+  }
+}
+
+TEST_F(SeaPenWallpaperManagerTest, GetTemplateIdFromFileFailure) {
+  const uint32_t image_id = 88888888;
+
+  {
+    base::test::TestFuture<bool> save_image_future;
+    sea_pen_wallpaper_manager()->SaveSeaPenImage(
+        kAccountId1, {CreateJpgBytes(), image_id},
+        personalization_app::mojom::SeaPenQuery::NewTextQuery("test query"),
+        save_image_future.GetCallback());
+    ASSERT_TRUE(save_image_future.Get());
+  }
+
+  {
+    base::test::TestFuture<std::optional<int>> get_template_id_future;
+    sea_pen_wallpaper_manager()->GetTemplateIdFromFile(
+        kAccountId1, image_id, get_template_id_future.GetCallback());
+    EXPECT_FALSE(get_template_id_future.Get());
+  }
+}
+
 TEST_F(SeaPenWallpaperManagerTest, DeleteNonExistentImage) {
   // File does not exist yet. Deleting it should fail.
   base::test::TestFuture<bool> delete_sea_pen_image_future;
diff --git a/ash/wallpaper/test_sea_pen_wallpaper_manager_session_delegate.cc b/ash/wallpaper/test_sea_pen_wallpaper_manager_session_delegate.cc
index 086f48b..369f86d 100644
--- a/ash/wallpaper/test_sea_pen_wallpaper_manager_session_delegate.cc
+++ b/ash/wallpaper/test_sea_pen_wallpaper_manager_session_delegate.cc
@@ -9,6 +9,7 @@
 #include "ash/wallpaper/sea_pen_wallpaper_manager.h"
 #include "ash/wallpaper/wallpaper_constants.h"
 #include "base/files/file_path.h"
+#include "base/logging.h"
 #include "components/account_id/account_id.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -25,8 +26,13 @@
   if (!scoped_temp_dir_.IsValid()) {
     EXPECT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
   }
+  // Public account and guest users do not have an account id key.
+  const std::string account_identifier = account_id.HasAccountIdKey()
+                                             ? account_id.GetAccountIdKey()
+                                             : account_id.GetUserEmail();
+  EXPECT_NE(std::string(), account_identifier);
   return scoped_temp_dir_.GetPath()
-      .Append(account_id.GetAccountIdKey())
+      .Append(account_identifier)
       .Append("wallpaper")
       .Append(wallpaper_constants::kSeaPenWallpaperDirName);
 }
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index db515c4..49c77dc 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -13,6 +13,7 @@
 #include "ash/display/window_tree_host_manager.h"
 #include "ash/login/login_screen_controller.h"
 #include "ash/public/cpp/image_downloader.h"
+#include "ash/public/cpp/image_util.h"
 #include "ash/public/cpp/schedule_enums.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/wallpaper/google_photos_wallpaper_params.h"
@@ -2390,6 +2391,20 @@
     return;
   }
 
+  if (IsEphemeralUser(account_id)) {
+    DCHECK(features::IsSeaPenDemoModeEnabled());
+    // Demo mode users are eligible for SeaPen but should not save the wallpaper
+    // image. Set a fake file path to use for the in memory wallpaper cache.
+    const auto cache_file_path =
+        base::FilePath("in_memory_cache")
+            .Append(wallpaper_constants::kSeaPenWallpaperDirName)
+            .Append(base::NumberToString(sea_pen_image_id))
+            .AddExtension(".jpg");
+    OnSeaPenWallpaperSavedToPublic(account_id, image_skia, sea_pen_image_id,
+                                   std::move(callback), cache_file_path);
+    return;
+  }
+
   // Save a copy of the currently selected SeaPen wallpaper in the global
   // wallpaper directory so that it is available on lock screen.
   wallpaper_file_manager_->SaveWallpaperToDisk(
diff --git a/ash/wallpaper/wallpaper_controller_unittest.cc b/ash/wallpaper/wallpaper_controller_unittest.cc
index 3f1731aa..f3308dd 100644
--- a/ash/wallpaper/wallpaper_controller_unittest.cc
+++ b/ash/wallpaper/wallpaper_controller_unittest.cc
@@ -58,6 +58,7 @@
 #include "ash/wm/window_state.h"
 #include "base/command_line.h"
 #include "base/containers/flat_map.h"
+#include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/functional/bind.h"
@@ -85,6 +86,7 @@
 #include "base/time/time_override.h"
 #include "chromeos/ash/components/geolocation/simple_geolocation_provider.h"
 #include "chromeos/constants/chromeos_features.h"
+#include "components/account_id/account_id.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "components/user_manager/user_names.h"
@@ -854,7 +856,10 @@
     RunAllTasksUntilIdle();
   }
 
-  void SetSeaPenWallpaper(gfx::ImageSkia* image, SkColor color, uint32_t id) {
+  void SetSeaPenWallpaper(const AccountId& account_id,
+                          SkColor color,
+                          uint32_t id,
+                          gfx::ImageSkia* image) {
     TestWallpaperControllerObserver observer(controller_);
     std::string jpg_bytes = CreateEncodedImageForTesting(
         {1, 1}, color, data_decoder::mojom::ImageCodec::kDefault, image);
@@ -863,13 +868,13 @@
     base::test::TestFuture<bool> save_sea_pen_image_future;
     auto* sea_pen_wallpaper_manager = SeaPenWallpaperManager::GetInstance();
     sea_pen_wallpaper_manager->SaveSeaPenImage(
-        kAccountId1, {std::move(jpg_bytes), id},
+        account_id, {std::move(jpg_bytes), id},
         personalization_app::mojom::SeaPenQuery::NewTextQuery("search_query"),
         save_sea_pen_image_future.GetCallback());
     ASSERT_TRUE(save_sea_pen_image_future.Get());
 
     base::test::TestFuture<bool> set_wallpaper_future;
-    controller_->SetSeaPenWallpaper(kAccountId1, id,
+    controller_->SetSeaPenWallpaper(account_id, id,
                                     set_wallpaper_future.GetCallback());
 
     EXPECT_TRUE(set_wallpaper_future.Take());
@@ -949,6 +954,7 @@
     }
     enabled_features.push_back(features::kSeaPen);
     enabled_features.push_back(features::kFeatureManagementSeaPen);
+    enabled_features.push_back(features::kSeaPenDemoMode);
     scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
@@ -2127,7 +2133,7 @@
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
 
   gfx::ImageSkia expected_image;
-  SetSeaPenWallpaper(&expected_image, SK_ColorGREEN, /*id=*/777u);
+  SetSeaPenWallpaper(kAccountId1, SK_ColorGREEN, /*id=*/777u, &expected_image);
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   EXPECT_EQ(WallpaperType::kSeaPen, wallpaper_info.type);
@@ -2140,13 +2146,24 @@
       *expected_image.bitmap(), *controller_->GetWallpaperImage().bitmap(),
       /*max_deviation=*/1));
 
-  // Expects the wallpaper is saved in global dir.
-  base::FilePath saved_wallpaper = online_wallpaper_dir_.GetPath()
-                                       .Append("sea_pen")
-                                       .Append(kAccountId1.GetAccountIdKey())
-                                       .Append(wallpaper_info.location)
-                                       .ReplaceExtension(".jpg");
-  ASSERT_TRUE(base::PathExists(saved_wallpaper));
+  base::FileEnumerator file_enumerator(online_wallpaper_dir_.GetPath(),
+                                       /*recursive=*/true,
+                                       base::FileEnumerator::FileType::FILES);
+
+  std::vector<base::FilePath> wallpaper_files;
+  for (auto path = file_enumerator.Next(); !path.empty();
+       path = file_enumerator.Next()) {
+    wallpaper_files.push_back(path);
+  }
+
+  // One SeaPen image file saved to global wallpaper directory for account.
+  EXPECT_EQ(std::vector<base::FilePath>(
+                {base::FilePath(online_wallpaper_dir_.GetPath())
+                     .Append(wallpaper_constants::kSeaPenWallpaperDirName)
+                     .Append(kAccountId1.GetAccountIdKey())
+                     .Append("777")
+                     .AddExtension(".jpg")}),
+            wallpaper_files);
 }
 
 TEST_P(WallpaperControllerTest,
@@ -2164,7 +2181,8 @@
   {
     // Sets a sea pen wallpaper.
     gfx::ImageSkia expected_image;
-    SetSeaPenWallpaper(&expected_image, SK_ColorGREEN, /*id=*/848u);
+    SetSeaPenWallpaper(kAccountId1, SK_ColorGREEN, /*id=*/848u,
+                       &expected_image);
     EXPECT_TRUE(
         pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
     EXPECT_EQ(WallpaperType::kSeaPen, wallpaper_info.type);
@@ -2213,7 +2231,7 @@
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
 
   gfx::ImageSkia expected_image;
-  SetSeaPenWallpaper(&expected_image, SK_ColorBLUE, 888u);
+  SetSeaPenWallpaper(kAccountId1, SK_ColorBLUE, 888u, &expected_image);
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   EXPECT_EQ(WallpaperType::kSeaPen, wallpaper_info.type);
@@ -2329,8 +2347,9 @@
   constexpr std::array<uint32_t, 2> kImageIds = {888, 999};
 
   const auto global_sea_pen_dir =
-      online_wallpaper_dir_.GetPath().Append("sea_pen").Append(
-          kAccountId1.GetAccountIdKey());
+      online_wallpaper_dir_.GetPath()
+          .Append(wallpaper_constants::kSeaPenWallpaperDirName)
+          .Append(kAccountId1.GetAccountIdKey());
   ASSERT_TRUE(base::CreateDirectory(global_sea_pen_dir));
 
   {
@@ -2421,6 +2440,40 @@
   }
 }
 
+TEST_P(WallpaperControllerTest, SetSeaPenWallpaperForPublicAccount) {
+  ClearLogin();
+
+  const AccountId account_id = AccountId::FromUserEmail("public_session");
+  SimulateUserLogin(account_id, user_manager::UserType::kPublicAccount);
+
+  gfx::ImageSkia expected_image;
+  SetSeaPenWallpaper(account_id, SK_ColorBLUE, 12345u, &expected_image);
+
+  WallpaperInfo wallpaper_info;
+  ASSERT_TRUE(pref_manager_->GetUserWallpaperInfo(account_id, &wallpaper_info));
+  EXPECT_EQ(WallpaperType::kSeaPen, wallpaper_info.type);
+  EXPECT_EQ("12345", wallpaper_info.location);
+
+  // Use `AreBitmapsClose` because jpg encoding/decoding can alter the color
+  // channels +- 1.
+  EXPECT_TRUE(gfx::test::AreBitmapsClose(
+      *expected_image.bitmap(), *controller_->GetWallpaperImage().bitmap(),
+      /*max_deviation=*/1));
+
+  base::FileEnumerator file_enumerator(online_wallpaper_dir_.GetPath(),
+                                       /*recursive=*/true,
+                                       base::FileEnumerator::FileType::FILES);
+
+  std::vector<base::FilePath> wallpaper_files;
+  for (auto path = file_enumerator.Next(); !path.empty();
+       path = file_enumerator.Next()) {
+    wallpaper_files.push_back(path);
+  }
+
+  // No wallpaper files saved to global wallpaper directory for public account.
+  EXPECT_TRUE(wallpaper_files.empty());
+}
+
 TEST_P(WallpaperControllerTest, SetDefaultWallpaperForRegularAccount) {
   gfx::ImageSkia image = CreateImage(640, 480, kWallpaperColor);
   SimulateUserLogin(kAccountId1);
diff --git a/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.cc b/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.cc
index 4c792bb..d3041d2 100644
--- a/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.cc
+++ b/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.cc
@@ -97,6 +97,21 @@
       GetCreationTimeInfo(*creation_time));
 }
 
+std::optional<int> ExtractTemplateIdFromSeaPenQueryDict(
+    const std::optional<base::Value::Dict> query_dict) {
+  if (!query_dict.has_value()) {
+    DVLOG(2) << __func__ << " query_dict nullopt";
+    return std::nullopt;
+  }
+
+  int template_id;
+  auto* template_id_ptr = query_dict->FindString(kSeaPenTemplateIdKey);
+  if (!template_id_ptr || !base::StringToInt(*template_id_ptr, &template_id)) {
+    return std::nullopt;
+  }
+  return template_id;
+}
+
 }  // namespace
 
 base::Value::Dict SeaPenQueryToDict(
@@ -162,6 +177,15 @@
                 .Then(std::move(callback)));
 }
 
+void DecodeJsonMetadataGetTemplateId(
+    const std::string& json,
+    base::OnceCallback<void(std::optional<int>)> callback) {
+  data_decoder::DataDecoder::ParseJsonIsolated(
+      json, base::BindOnce(&AsOptionalDict)
+                .Then(base::BindOnce(&ExtractTemplateIdFromSeaPenQueryDict))
+                .Then(std::move(callback)));
+}
+
 std::optional<uint32_t> GetIdFromFileName(const base::FilePath& file_path) {
   const std::string name = file_path.BaseName().RemoveExtension().value();
   uint32_t value;
diff --git a/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h b/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h
index 4b02d437a..5ab0bd0e 100644
--- a/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h
+++ b/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h
@@ -67,6 +67,13 @@
     base::OnceCallback<
         void(personalization_app::mojom::RecentSeaPenImageInfoPtr)> callback);
 
+// Decodes the SeaPen metadata `json` string and extracts the template id from
+// it. Calls `callback` with nullopt if metadata is invalid or cannot be safely
+// decoded. `json` must not be wrapped in XMP metadata XML tags.
+ASH_EXPORT void DecodeJsonMetadataGetTemplateId(
+    const std::string& json,
+    base::OnceCallback<void(std::optional<int>)> callback);
+
 // Extract the id from a sea pen file name. SeaPen images must be saved to disk
 // as `/path/to/file/{id}.jpg` where id is a positive integer. `file_path` can
 // either include or omit the extension, and can include or omit leading
diff --git a/ash/webui/common/resources/cellular_setup/activation_code_page.ts b/ash/webui/common/resources/cellular_setup/activation_code_page.ts
index e4b23bf0..97bea06c 100644
--- a/ash/webui/common/resources/cellular_setup/activation_code_page.ts
+++ b/ash/webui/common/resources/cellular_setup/activation_code_page.ts
@@ -13,6 +13,7 @@
 import './base_page.js';
 import './cellular_setup_icons.html.js';
 
+import type {CrButtonElement} from '//resources/ash/common/cr_elements/cr_button/cr_button.js';
 import {CrInputElement} from '//resources/ash/common/cr_elements/cr_input/cr_input.js';
 import {I18nMixin} from '//resources/ash/common/cr_elements/i18n_mixin.js';
 import {MojoInterfaceProviderImpl} from '//resources/ash/common/network/mojo_interface_provider.js';
@@ -334,6 +335,30 @@
     return this.qrCodeDetectorTimer_;
   }
 
+  attemptToFocusOnPageContent(): boolean {
+    // Prioritize focusing the camera button if scanning is available.
+    // TODO(b/332925540): Add interactive test for button focus.
+    if (this.isScanningAvailable_()) {
+      const useCameraBtn = this.shadowRoot!.querySelector<CrButtonElement>(
+          '#startScanningButton');
+
+      if (useCameraBtn) {
+        useCameraBtn.focus();
+        return true;
+      }
+    }
+
+    // Fallback: Focus on the activation code input
+    const activationCodeInput =
+        this.shadowRoot!.querySelector<CrInputElement>('#activationCode');
+    if (activationCodeInput) {
+      activationCodeInput.focus();
+      return true;
+    }
+
+    return false;
+  }
+
   private computeActivationCodeClass_(): string {
     return this.isScanningAvailable_() ? 'relative' : 'center';
   }
diff --git a/ash/webui/common/resources/cellular_setup/esim_flow_ui.ts b/ash/webui/common/resources/cellular_setup/esim_flow_ui.ts
index 9c49561..74189a9 100644
--- a/ash/webui/common/resources/cellular_setup/esim_flow_ui.ts
+++ b/ash/webui/common/resources/cellular_setup/esim_flow_ui.ts
@@ -24,6 +24,7 @@
 import {ConnectionStateType, NetworkType} from '//resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
 import {mixinBehaviors, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {ActivationCodePageElement} from './activation_code_page.js';
 import {CellularSetupDelegate} from './cellular_setup_delegate.js';
 import {ButtonBarState, ButtonState} from './cellular_types.js';
 import {getTemplate} from './esim_flow_ui.html.js';
@@ -848,18 +849,29 @@
 
   /** SubflowMixin override */
   override maybeFocusPageElement(): boolean {
-    if (this.state_ !== EsimUiState.PROFILE_SELECTION) {
-      return false;
-    }
+    switch (this.state_) {
+      case EsimUiState.ACTIVATION_CODE_ENTRY:
+      case EsimUiState.ACTIVATION_CODE_ENTRY_READY:
+        const activationCodePage =
+            this.shadowRoot!.querySelector<ActivationCodePageElement>(
+                '#activationCodePage');
 
-    const profileDiscoveryPage =
-        this.shadowRoot!.querySelector<ProfileDiscoveryListPageElement>(
-            '#profileDiscoveryPage');
+        if (!activationCodePage) {
+          return false;
+        }
+        return activationCodePage!.attemptToFocusOnPageContent();
+      case EsimUiState.PROFILE_SELECTION:
+        const profileDiscoveryPage =
+            this.shadowRoot!.querySelector<ProfileDiscoveryListPageElement>(
+                '#profileDiscoveryPage');
 
-    if (!profileDiscoveryPage) {
-      return false;
+        if (!profileDiscoveryPage) {
+          return false;
+        }
+        return profileDiscoveryPage.attemptToFocusOnFirstProfile();
+      default:
+        return false;
     }
-    return profileDiscoveryPage!.attemptToFocusOnFirstProfile();
   }
 
   private onForwardNavigationRequested_(): void {
diff --git a/ash/webui/common/resources/network/apn_list.html b/ash/webui/common/resources/network/apn_list.html
index f137399..e02150f 100644
--- a/ash/webui/common/resources/network/apn_list.html
+++ b/ash/webui/common/resources/network/apn_list.html
@@ -39,7 +39,7 @@
     margin-inline-end: 18px;
   }
 
-  #zeroStateText {
+  #zeroStateContent {
     border-top: var(--cr-separator-line);
     display: flex;
     margin-inline-end: 40px;
@@ -56,6 +56,7 @@
 <div id="apnDescription" class="property-box" aria-live="assertive">
   <template is="dom-if" if="[[!shouldOmitLinks]]" restamp>
     <localized-link
+        id="descriptionWithLink"
         localized-string="[[i18nAdvanced('apnSettingsDescriptionWithLink')]]">
     </localized-link>
   </template>
@@ -77,11 +78,15 @@
 </div>
 
 <template is="dom-if"
-    if="[[shouldShowZeroStateText_(managedCellularProperties, errorState)]]"
+    if="[[shouldShowZeroStateContent_(managedCellularProperties, errorState)]]"
     restamp>
-  <div id="zeroStateText">
+  <div id="zeroStateContent">
     <span><iron-icon icon="cr:info-outline"></iron-icon></span>
-    <div>[[i18n('apnSettingsZeroStateDescription')]]</div>
+      <localized-link
+          id="apnSettingsZeroStateDescriptionWithAddLink"
+          on-link-clicked="onZeroStateCreateApnLinkClicked_"
+          localized-string="[[i18nAdvanced('apnSettingsZeroStateDescriptionWithAddLink')]]">
+      </localized-link>
   </div>
 </template>
 
@@ -112,6 +117,7 @@
 
 <template is="dom-if" if="[[shouldShowApnSelectionDialog_]]" restamp>
   <apn-selection-dialog
+      should-omit-links="[[shouldOmitLinks]]"
       guid="[[guid]]"
       apn-list="[[getValidDatabaseApnList_(managedCellularProperties)]]"
       on-close="onApnSelectionDialogClose_">
diff --git a/ash/webui/common/resources/network/apn_list.js b/ash/webui/common/resources/network/apn_list.js
index e19fd590..87c869a 100644
--- a/ash/webui/common/resources/network/apn_list.js
+++ b/ash/webui/common/resources/network/apn_list.js
@@ -105,6 +105,18 @@
     };
   }
 
+  /**
+   * @param {!Event} e
+   * @private
+   */
+  onZeroStateCreateApnLinkClicked_(e) {
+    // A place holder href with the value "#" is used to have a compliant link.
+    // This prevents the browser from navigating the window to "#"
+    e.detail.event.preventDefault();
+    e.stopPropagation();
+    this.openApnDetailDialogInCreateMode();
+  }
+
   openApnDetailDialogInCreateMode() {
     this.showApnDetailDialog_(ApnDetailDialogMode.CREATE, /* apn= */ undefined);
   }
@@ -117,7 +129,7 @@
    * @return {boolean}
    * @private
    */
-  shouldShowZeroStateText_() {
+  shouldShowZeroStateContent_() {
     if (!this.managedCellularProperties) {
       return true;
     }
diff --git a/ash/webui/common/resources/network/apn_selection_dialog.html b/ash/webui/common/resources/network/apn_selection_dialog.html
index 00c6c66..8a47e18 100644
--- a/ash/webui/common/resources/network/apn_selection_dialog.html
+++ b/ash/webui/common/resources/network/apn_selection_dialog.html
@@ -28,9 +28,16 @@
     [[i18n('apnSelectionDialogTitle')]]
   </div>
   <div slot="body">
-    <div id="apnSelectionDialogDescription" aria-live="polite">
-      [[i18n('apnSelectionDialogDescription')]]
-    </div>
+    <template is="dom-if" if="[[!shouldOmitLinks]]" restamp>
+      <localized-link
+          localized-string="[[i18nAdvanced('apnSelectionDialogDescriptionWithLink')]]">
+      </localized-link>
+    </template>
+    <template is="dom-if" if="[[shouldOmitLinks]]" restamp>
+      <div id="apnSelectionDialogDescription" aria-live="polite">
+        [[i18n('apnSelectionDialogDescription')]]
+      </div>
+    </template>
     <div id="container" class="layout vertical flex" scrollable>
       <iron-list items="[[apnList]]"
           selection-enabled
diff --git a/ash/webui/common/resources/network/apn_selection_dialog.js b/ash/webui/common/resources/network/apn_selection_dialog.js
index 0392765..95501a6 100644
--- a/ash/webui/common/resources/network/apn_selection_dialog.js
+++ b/ash/webui/common/resources/network/apn_selection_dialog.js
@@ -7,6 +7,7 @@
  * Dialog that displays APNs for a user to select to be attempted to be used.
  */
 
+import '//resources/ash/common/cr_elements/localized_link/localized_link.js';
 import '//resources/ash/common/cr_elements/cr_button/cr_button.js';
 import '//resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
 import '//resources/ash/common/cr_elements/cr_shared_style.css.js';
@@ -51,6 +52,11 @@
       /** The GUID of the network to select known APNs for. */
       guid: String,
 
+      shouldOmitLinks: {
+        type: Boolean,
+        value: false,
+      },
+
       /** @type {ApnProperties} */
       selectedApn_: {
         type: Object,
diff --git a/ash/webui/common/resources/personalization/common.css b/ash/webui/common/resources/personalization/common.css
index 2ca3f96..222dca21 100644
--- a/ash/webui/common/resources/personalization/common.css
+++ b/ash/webui/common/resources/personalization/common.css
@@ -310,3 +310,7 @@
   /* Prevent jank due to overscrolling from the dialog */
   overscroll-behavior: contain;
 }
+
+a[href] {
+  color: var(--cros-sys-primary);
+}
diff --git a/ash/webui/common/resources/personalization/personalization_shared_icons.html b/ash/webui/common/resources/personalization/personalization_shared_icons.html
index a8182ce..97079c0b 100644
--- a/ash/webui/common/resources/personalization/personalization_shared_icons.html
+++ b/ash/webui/common/resources/personalization/personalization_shared_icons.html
@@ -44,17 +44,38 @@
 <iron-iconset-svg name="personalization-shared-illo">
   <svg>
     <defs>
-      <g id="network_error" width="600" height="280" viewBox="0 0 600 280" fill="none">
-        <path d="m192.7 214.3-3.9-11.3c-.7-2.1.4-4.3 2.4-5l11.3-3.9c2.1-.7 4.3.4 5 2.4l3.9 11.3c.7 2.1-.4 4.3-2.4 5l-11.3 3.9c-2.1.7-4.3-.4-5-2.4Z" fill="var(--cros-sys-illo-color3)"/><path d="M154 136.299c-1.3-.4-2.7-.6-4-.6l-4.3-.7c-3.7-.6-6.7-2.5-9.1-5.2-3.4-3.8-5.6-8.5-11-9.6-4.3-.9-8.8.6-11.7 3.8-.2.2-.3.3-.4.5-4.8 6-2.8 14.7 3.7 18.4 1.9 1.1 4 1.8 5.9 2.7 3.5 1.5 6.3 4.4 7.7 8l1.6 4.1c.3 1.3.8 2.6 1.5 3.8 1.3 2.5 3.3 4.7 5.9 6.4 7.1 4.7 16.8 3.5 22.6-2.9 6.9-7.6 5.8-19.2-2-25.5-2-1.5-4.2-2.6-6.4-3.2Z" stroke="var(--cros-sys-illo-color2)" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M556.5 136.6a7.3 7.3 0 0 0 7.3-7.3 7.3 7.3 0 0 0-7.3-7.3 7.3 7.3 0 0 0-7.3 7.3 7.3 7.3 0 0 0 7.3 7.3Z" fill="var(--cros-sys-illo-color5)"/><path d="M466.4 196.3a4.4 4.4 0 1 0 0-8.8 4.4 4.4 0 0 0 0 8.8Z" fill="var(--cros-sys-illo-color3)"/><path d="M38.7 107.7a3.1 3.1 0 1 0 0-6.2 3.1 3.1 0 0 0 0 6.2Z" fill="var(--cros-sys-illo-color1)"/><path d="m396.3 180.399-11 7.3c-1.8 1.2-2.8 3.2-2.6 5.3l.8 13.2c.1 2.1 1.4 4 3.3 4.9l11.8 5.9c1.9.9 4.1.8 5.9-.4l11-7.3c1.8-1.2 2.8-3.2 2.6-5.3l-.8-13.2c-.1-2.1-1.4-4-3.3-4.9l-11.8-5.9c-1.9-.9-4.2-.8-5.9.4Z" stroke="var(--cros-sys-illo-secondary)" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M73.7 161.4H54.3c-.9 0-1.5 1-1 1.8L63 180c.5.8 1.6.8 2 0l9.7-16.9c.5-.7 0-1.7-1-1.7Z" fill="var(--cros-sys-illo-color4)"/><path d="M454.1 144.599c-.9.9-.8 2.4.2 3.2 6.3 4.7 15.3 4.3 21.1-1.5 5.8-5.7 6.4-14.7 1.7-21.1-.8-1-2.2-1.2-3.1-.3l-19.9 19.7Z" fill="var(--cros-sys-illo-color1-2)"/><path d="M481.8 137.2c-9.2 1.8-18.1-4.4-19.7-13.6-3.3-22 27.9-28 32.9-6.3 2 9.2-4 18.1-13.2 19.9Z" stroke="var(--cros-sys-illo-secondary)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="3.92 3.92"/><path d="m385.6 126.3-11.8-11.8c-40.7-40.7-106.8-40.7-147.5 0l-11.8 11.8c-3.8 3.8-3.8 10 0 13.8l20.3 20.3c.3-.4.6-.8.9-1.1l8.9-8.9c30.7-30.7 80.4-30.7 111 0l8.9 8.9c.3.3.6.7.9 1.1l20.3-20.3c3.7-3.8 3.7-10-.1-13.8Z" fill="var(--cros-sys-illo-color1-2)"/><path d="M355.5 150.3c-30.7-30.7-80.4-30.7-111 0l-8.9 8.9c-.3.3-.6.7-.9 1.1l27.6 27.6 2.3-2.3c19.5-19.5 51.2-19.5 70.8 0l2.3 2.3 27.6-27.6c-.3-.4-.6-.8-.9-1.1l-8.9-8.9Z" fill="var(--cros-sys-illo-color1-1)"/><path d="m306.901 218.799 30.8-30.8-2.3-2.3c-19.5-19.5-51.2-19.5-70.8 0l-2.3 2.3 30.8 30.8c3.8 3.8 10 3.8 13.8 0ZM466.2 134.1c1 1 2.2 1.8 3.4 2.5.2.1.5.2.7.2.5 0 1-.3 1.3-.8.4-.7.2-1.6-.6-2-1-.6-2-1.3-2.9-2.1-.3-.3-.8-.4-1.2-.4l-1.3 1.3c.1.5.3 1 .6 1.3ZM477.9 136.001c-.8 0-1.5.6-1.6 1.4 0 .8.6 1.5 1.4 1.6h.6c.4 0 .9 0 1.3-.1.3-1 .5-2 .5-3.1-.6.2-1.4.2-2.2.2Z" fill="var(--cros-sys-illo-color1)"/>
+      <g id="generic_error" width="200" height="100" viewBox="0 0 200 100" fill="none" clip-path="url(#clip0_2195_26319)">
+        <path d="M159.075 41.481L137.937 24.8853C135.307 22.8697 133.574 19.8651 133.119 16.5318C132.664 13.1985 133.525 9.80944 135.511 7.10954C136.495 5.77196 137.725 4.64398 139.133 3.78998C140.541 2.93607 142.098 2.37281 143.715 2.13248C145.332 1.89214 146.978 1.97943 148.559 2.38933C150.14 2.79931 151.624 3.52384 152.927 4.52159L174.058 21.1163C176.69 23.131 178.424 26.1354 178.88 29.4689C179.337 32.8024 178.477 36.1922 176.49 38.893C174.503 41.5929 171.552 43.3821 168.286 43.8674C165.02 44.3527 161.707 43.4943 159.075 41.481Z" fill="var(--cros-sys-illo-secondary)" />
+        <path d="M38.7502 23.1417L29.7484 33.8612C28.6129 35.2134 28.7894 37.2292 30.1426 38.3635L40.7437 47.2518C42.0969 48.3868 44.1144 48.2105 45.2498 46.8584L54.2515 36.1386C55.3874 34.7866 55.2103 32.7707 53.8571 31.6362L43.2563 22.7478C41.9032 21.6132 39.8857 21.7895 38.7502 23.1417Z" fill="var(--cros-sys-illo-color4)" />
+        <path d="M57.8929 92.104C60.9634 91.4255 63.9958 90.5816 66.9762 89.5758C71.1224 88.3328 75.5794 88.7553 79.3682 90.7498C81.4382 91.9432 83.5718 93.026 85.7611 93.9942C93.5586 97.0549 102.599 92.9034 104.631 84.6198C106.417 77.3437 101.536 69.4587 93.928 68.332C90.413 67.7949 86.8396 68.4626 83.3059 68.2362C79.5443 68.0349 75.9874 66.5496 73.2446 64.0357C70.9002 61.8616 68.7508 59.4748 66.107 57.5949C63.5447 55.7538 60.5505 54.574 57.3929 54.1612C52.4041 53.5215 47.3326 54.8004 43.2294 57.7323C39.1263 60.6642 36.3062 65.0244 35.3537 69.9091C34.4012 74.7944 35.3893 79.8291 38.1133 83.9704C40.8373 88.1122 45.0881 91.0423 49.9848 92.1542C52.5885 92.6816 55.2824 92.6645 57.8929 92.104Z" fill="var(--cros-sys-illo-color1-2)" />
+        <path d="M146.019 95.1237C145.717 95.3808 145.365 95.5701 144.983 95.6795C144.602 95.7891 144.201 95.8165 143.808 95.7597C143.414 95.7027 143.035 95.5624 142.698 95.3485C142.361 95.1349 142.071 94.8525 141.849 94.519C137.93 88.697 136.272 81.6476 137.192 74.7205C138.112 67.7925 141.545 61.473 146.833 56.9712C152.122 52.4687 158.896 50.1001 165.858 50.3184C172.821 50.5365 179.483 53.3259 184.57 58.1531C184.861 58.4285 185.091 58.7618 185.246 59.1312C185.401 59.5008 185.477 59.898 185.469 60.2974C185.462 60.697 185.37 61.0896 185.202 61.4494C185.032 61.8095 184.789 62.1293 184.489 62.3875L146.019 95.1237Z" stroke="var(--cros-sys-illo-secondary)" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4 6" />
+        <path d="M122 82C126.97 82 131 77.9704 131 73C131 68.0292 126.97 64 122 64C117.029 64 113 68.0292 113 73C113 77.9704 117.029 82 122 82Z" stroke="var(--cros-sys-illo-color5)" stroke-width="3" stroke-miterlimit="10" />
+        <path d="M97 65C114.673 65 129 50.6731 129 33C129 15.3269 114.673 1 97 1C79.3269 1 65 15.3269 65 33C65 50.6731 79.3269 65 97 65Z" fill="var(--cros-sys-illo-color1-1)" />
+        <path fill-rule="evenodd" clip-rule="evenodd" d="M88.1366 40.7918C87.3907 41.6065 87.4464 42.8716 88.2611 43.6175C89.0758 44.3634 90.3409 44.3076 91.0868 43.4929L97.5324 36.453L104.44 43.0403C105.239 43.8026 106.505 43.7726 107.268 42.9732C108.03 42.1738 108 40.9079 107.201 40.1456L100.234 33.5021L106.735 26.402C107.481 25.5873 107.425 24.3222 106.61 23.5763C105.795 22.8304 104.53 22.8862 103.784 23.7009L97.3387 30.7411L90.4311 24.1538C89.6317 23.3915 88.3657 23.4215 87.6034 24.2209C86.8411 25.0203 86.8712 26.2862 87.6706 27.0485L94.637 33.6919L88.1366 40.7918Z" fill="var(--cros-sys-base_elevated)" />
       </g>
-      <g id="resource_error" width="224" height="168" viewBox="0 0 224 168" fill="none">
-        <path d="M205.623 93.6191C208.937 93.6191 211.623 90.9328 211.623 87.6191C211.623 84.3054 208.937 81.6191 205.623 81.6191C202.309 81.6191 199.623 84.3054 199.623 87.6191C199.623 90.9328 202.309 93.6191 205.623 93.6191Z" fill="var(--cros-sys-illo-color3)" />
-        <path d="M25.249 48.1989L12.4038 57.8346C11.7872 58.2971 11.9022 59.1997 12.5757 59.4553L26.8856 65.1519C27.5591 65.4074 28.2784 64.8678 28.2215 64.1498L26.7568 48.8174C26.6634 48.0507 25.8141 47.7749 25.249 48.1989Z" fill="var(--cros-sys-illo-color1-2)" />
-        <path d="M203.257 61.5298C204.687 60.5778 204.867 58.6519 203.776 57.4002C196.85 49.7104 185.313 48.0135 176.391 53.8638C167.569 59.7341 164.483 70.9899 168.806 80.3256C169.54 81.8154 171.386 82.3926 172.698 81.52L203.257 61.5298Z" fill="var(--cros-sys-illo-color1-2)" />
-        <path d="M164.572 66.5443C176.567 66.4745 186.252 76.3704 186.184 88.542L186.223 88.8597C186.076 100.912 176.278 110.613 164.223 110.465C152.168 110.316 142.463 100.52 142.611 88.4675L142.571 88.1498C142.6 76.1767 152.497 66.495 164.572 66.5443Z" stroke="var(--cros-sys-illo-color1)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="3.61 3.61" />
-        <path d="M117.741 103.826C117.226 101.756 117.171 99.9452 117.577 98.3935C118.014 96.7901 119.035 94.7994 120.642 92.4214C121.887 90.5292 122.732 88.9781 123.179 87.7679C123.656 86.506 123.71 85.1298 123.339 83.6392C122.845 81.6517 121.807 80.2171 120.225 79.3355C118.632 78.4125 116.697 78.2341 114.42 78.8004C112.35 79.3151 110.863 80.3222 109.961 81.8215C109.049 83.2795 108.498 84.9552 108.309 86.8487L102.894 86.0191C103.125 83.4116 104.069 80.9344 105.728 78.5875C107.376 76.1992 109.857 74.5932 113.169 73.7696C115.653 73.1518 117.977 73.1237 120.139 73.6853C122.332 74.1952 124.167 75.2116 125.645 76.7347C127.164 78.2475 128.197 80.1011 128.743 82.2956C129.134 83.869 129.168 85.4213 128.845 86.9524C128.512 88.4422 128.06 89.7196 127.49 90.7847C126.951 91.7982 126.219 93.0131 125.297 94.4297C124.239 96.0116 123.518 97.4439 123.134 98.7266C122.79 99.999 122.804 101.38 123.175 102.871L123.823 105.479L118.482 106.807L117.741 103.826ZM124.504 119.621C123.427 119.889 122.403 119.748 121.431 119.198C120.49 118.597 119.881 117.737 119.603 116.619C119.335 115.542 119.481 114.539 120.041 113.608C120.632 112.626 121.466 112.001 122.543 111.733C123.661 111.455 124.69 111.617 125.631 112.218C126.603 112.768 127.223 113.581 127.491 114.658C127.769 115.776 127.612 116.826 127.021 117.808C126.461 118.739 125.622 119.343 124.504 119.621Z" fill="var(--cros-sys-illo-color1-2)" />
-        <path d="M68.9633 111.118L63.8999 96.4373C62.9885 93.805 64.4062 90.869 67.0392 89.9578L81.723 84.8955C84.3559 83.9843 87.2927 85.4018 88.2041 88.0342L93.2675 102.714C94.1789 105.347 92.7612 108.283 90.1282 109.194L75.4444 114.256C72.8114 115.167 69.8747 113.75 68.9633 111.118Z" fill="var(--cros-sys-illo-color1)" />
-        <path d="M48.4669 59.9146L34.0869 69.4315C31.7577 70.9502 30.4413 73.5826 30.6438 76.3162L31.6565 93.5277C31.859 96.2613 33.4793 98.6912 35.9097 99.9061L51.3024 107.6C53.7328 108.815 56.6696 108.613 58.9988 107.094L73.3787 97.5775C75.7079 96.0588 77.0244 93.4265 76.8218 90.6929L75.8092 73.4813C75.6066 70.7477 73.9864 68.3179 71.5559 67.1029L56.1632 59.4084C53.6315 58.1934 50.6948 58.3959 48.4669 59.9146Z" stroke="var(--cros-sys-illo-secondary)" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
+      <clipPath id="clip0_2195_26319">
+        <rect width="200" height="100" fill="var(--cros-sys-base_elevated)" />
+      </clipPath>
+      <g id="network_error" width="200" height="100" viewBox="0 0 200 100" fill="none">
+        <path d="M168.5 23.8782L161.576 32.124C160.702 33.1642 160.838 34.7148 161.879 35.5873L170.034 42.4245C171.075 43.2976 172.626 43.1619 173.5 42.1219L180.424 33.8759C181.298 32.8358 181.162 31.2852 180.121 30.4124L171.966 23.5752C170.926 22.7025 169.374 22.8381 168.5 23.8782Z" fill="var(--cros-sys-illo-color5)" />
+        <path d="M34.0445 72.2089C36.4477 72.3263 38.8567 72.3124 41.2608 72.1671C44.5731 72.0816 47.7718 73.3028 50.1543 75.5618C51.4346 76.8735 52.7847 78.1155 54.1993 79.2832C59.3175 83.153 66.8362 81.8904 70.0394 76.1187C72.853 71.049 70.8782 64.1701 65.5014 61.7828C63.0208 60.6675 60.2491 60.4398 57.6907 59.5528C54.9592 58.6381 52.6428 56.8063 51.1383 54.372C49.8575 52.2723 48.7643 50.0535 47.2022 48.1127C45.6923 46.2174 43.7278 44.7281 41.4851 43.7783C37.9391 42.2868 33.9373 42.2111 30.3091 43.5663C26.681 44.9216 23.7047 47.6039 21.9972 51.0574C20.2895 54.5114 19.9817 58.4712 21.1375 62.1167C22.2931 65.7626 24.8236 68.8141 28.2045 70.6393C30.0153 71.5622 32.0048 72.0969 34.0445 72.2089Z" fill="var(--cros-sys-illo-secondary)" />
+        <path d="M149.895 79.6822C149.656 79.8238 149.39 79.9127 149.114 79.943C148.839 79.9735 148.561 79.945 148.298 79.8592C148.035 79.7732 147.793 79.6316 147.588 79.444C147.384 79.2567 147.22 79.0275 147.11 78.7711C145.152 74.2896 144.899 69.2289 146.4 64.5579C147.9 59.8862 151.048 55.932 155.242 53.4518C159.435 50.9712 164.38 50.1388 169.129 51.1139C173.878 52.0888 178.099 54.8025 180.983 58.7359C181.148 58.9604 181.264 59.2177 181.324 59.4908C181.384 59.7643 181.386 60.0474 181.331 60.322C181.275 60.5968 181.163 60.8569 181.002 61.0852C180.841 61.3137 180.634 61.5055 180.396 61.6481L149.895 79.6822Z" fill="var(--cros-sys-illo-color1-2)" />
+        <path fill-rule="evenodd" clip-rule="evenodd" d="M77.186 24.4025C74.4997 26.2464 71.9441 28.3615 69.5578 30.7478L63.4703 36.8353C61.5099 38.7957 61.5099 41.9943 63.4703 43.9547L73.9429 54.4273L73.943 54.4272C74.0977 54.2209 74.2525 54.0146 74.4072 53.8598L78.9987 49.2684C83.3506 44.9165 88.4425 41.7604 93.8668 39.8001L77.186 24.4025ZM97.1571 38.7676C110.673 35.1522 125.682 38.6525 136.263 49.2684L140.854 53.8598C141.009 54.0145 141.164 54.2209 141.318 54.4272L141.319 54.4273L151.791 43.9547C153.7 41.9943 153.7 38.7957 151.74 36.8353L145.652 30.7478C127.799 12.8943 100.472 10.2214 79.7818 22.7289L97.1571 38.7676Z" fill="var(--cros-sys-illo-color1-2)" />
+        <path fill-rule="evenodd" clip-rule="evenodd" d="M93.8031 39.7414C88.3783 41.7012 83.29 44.8595 78.9474 49.2163L74.3559 53.8078C74.2011 53.9625 74.0464 54.1689 73.8916 54.3753L88.1302 68.6139L89.3168 67.4274C97.0521 59.6921 108.509 57.9046 117.987 62.065L93.8031 39.7414ZM128.21 67.4322L141.267 54.3753L141.267 54.3751C141.112 54.1688 140.958 53.9625 140.803 53.8078L136.212 49.2163C125.602 38.6064 110.593 35.1041 97.0942 38.7096L128.21 67.4322Z" fill="var(--cros-sys-illo-color1-1)" />
+        <path fill-rule="evenodd" clip-rule="evenodd" d="M118.068 62.1401C108.601 57.949 97.1062 59.7285 89.3164 67.4785L88.1299 68.6651L104.019 84.5546C105.98 86.515 109.178 86.515 111.139 84.5546L126.121 69.5728L118.068 62.1401Z" fill="var(--cros-sys-illo-color1)" />
+        <line x1="1.5" y1="-1.5" x2="98.3868" y2="-1.5" transform="matrix(0.734803 0.67828 -0.734803 0.67828 65.4268 17.3242)" stroke="var(--cros-sys-illo-color1)" stroke-width="3" stroke-linecap="round" />
+        <path d="M43 27C47.9706 27 52 22.9706 52 18C52 13.0294 47.9706 9 43 9C38.0294 9 34 13.0294 34 18C34 22.9706 38.0294 27 43 27Z" stroke="var(--cros-sys-illo-color1-2)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="3 6" />
+        <path d="M47 34C51.9704 34 56 29.9704 56 25C56 20.0292 51.9704 16 47 16C42.0292 16 38 20.0292 38 25C38 29.9704 42.0292 34 47 34Z" stroke="var(--cros-sys-illo-color4)" stroke-width="3" stroke-miterlimit="10" />
+      </g>
+      <g id="resource_error" width="200" height="100" viewBox="0 0 200 100" fill="none">
+        <path d="M142.439 29.4406L129.112 19.1671C127.455 17.9193 126.362 16.0593 126.075 13.9958C125.788 11.9324 126.331 9.83441 127.583 8.16305C128.203 7.33502 128.979 6.63675 129.867 6.10809C130.754 5.57947 131.736 5.23079 132.755 5.08201C133.775 4.93323 134.812 4.98726 135.809 5.24101C136.805 5.49481 137.741 5.94333 138.562 6.56098L151.885 16.8339C153.544 18.0811 154.637 19.9409 154.925 22.0046C155.212 24.0682 154.67 26.1666 153.418 27.8385C152.165 29.5099 150.304 30.6175 148.246 30.9179C146.187 31.2183 144.098 30.687 142.439 29.4406Z" fill="var(--cros-sys-illo-color3)" />
+        <path d="M36.7502 22.1417L27.7484 32.8612C26.6129 34.2134 26.7894 36.2292 28.1426 37.3635L38.7437 46.2518C40.0969 47.3868 42.1144 47.2105 43.2498 45.8584L52.2515 35.1386C53.3874 33.7866 53.2103 31.7707 51.8571 30.6362L41.2563 21.7478C39.9032 20.6132 37.8857 20.7895 36.7502 22.1417Z" fill="var(--cros-sys-illo-color5)" />
+        <path d="M31.2614 81.0473C33.3841 81.4242 35.5258 81.6867 37.6769 81.8335C40.6285 82.1363 43.3422 83.5715 45.222 85.8235C46.2219 87.1193 47.291 88.362 48.4253 89.547C52.5666 93.5229 59.3787 93.2735 62.8296 88.5792C65.8608 84.4558 64.8276 78.2005 60.3011 75.4947C58.2143 74.2341 55.7758 73.7185 53.5959 72.6491C51.2651 71.5359 49.3993 69.666 48.3179 67.3604C47.4002 65.3738 46.6616 63.3041 45.4774 61.4246C44.3347 59.591 42.7456 58.0615 40.8527 56.9731C37.8588 55.2614 34.3115 54.7387 30.9461 55.5129C27.5808 56.2872 24.6554 58.2992 22.7763 61.1319C20.8971 63.9649 20.2084 67.4011 20.853 70.7285C21.4973 74.0563 23.4255 77.0199 26.2377 79.0054C27.7497 80.0209 29.4611 80.7165 31.2614 81.0473Z" fill="var(--cros-sys-illo-color1-2)" />
+        <path d="M165.933 84.2934C165.762 84.6684 165.512 85.0026 165.199 85.2745C164.887 85.5463 164.519 85.7499 164.121 85.8712C163.721 85.9924 163.3 86.0285 162.884 85.9776C162.468 85.9267 162.067 85.7901 161.706 85.5758C155.38 81.8497 150.647 75.9685 148.415 69.0587C146.181 62.1482 146.604 54.6939 149.602 48.1222C152.6 41.5499 157.963 36.3217 164.664 33.4379C171.365 30.5539 178.935 30.2166 185.924 32.4905C186.323 32.6201 186.691 32.8307 187.003 33.1088C187.316 33.3869 187.566 33.7259 187.737 34.1043C187.91 34.4827 187.999 34.8919 188 35.3044C188.001 35.7176 187.914 36.1254 187.744 36.5011L165.933 84.2934Z" stroke="var(--cros-sys-illo-secondary)" stroke-width="3" stroke-miterlimit="10" />
+        <path d="M135.5 88C139.09 88 142 85.0897 142 81.5C142 77.9099 139.09 75 135.5 75C131.91 75 129 77.9099 129 81.5C129 85.0897 131.91 88 135.5 88Z" stroke="var(--cros-sys-illo-color4)" stroke-width="3" stroke-miterlimit="10" />
+        <path d="M96 86C116.435 86 133 69.4345 133 49C133 28.5655 116.435 12 96 12C75.5655 12 59 28.5655 59 49C59 69.4345 75.5655 86 96 86Z" stroke="var(--cros-sys-illo-color1-1)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4 8" />
+        <path d="M96 76C110.912 76 123 63.9117 123 49C123 34.0883 110.912 22 96 22C81.0883 22 69 34.0883 69 49C69 63.9117 81.0883 76 96 76Z" stroke="var(--cros-sys-illo-color1)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
+        <path d="M104.125 61.1179L95.9999 48.5427L102.409 39.386" stroke="var(--cros-sys-illo-color1)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
       </g>
     </defs>
   </svg>
diff --git a/ash/webui/common/resources/sea_pen/constants_generated.ts b/ash/webui/common/resources/sea_pen/constants_generated.ts
index 478be895..95e58c9 100644
--- a/ash/webui/common/resources/sea_pen/constants_generated.ts
+++ b/ash/webui/common/resources/sea_pen/constants_generated.ts
@@ -14,184 +14,94 @@
 export function getWallpaperTemplates(): SeaPenTemplate[] {
   return [
     {
-      id: SeaPenTemplateId.kTerrain,
-      title: loadTimeData.getString('seaPenTemplateTitleTerrain'),
+      id: SeaPenTemplateId.kGlowscapes,
+      title: loadTimeData.getString('seaPenTemplateTitleGlowscapes'),
       text: loadTimeData.getStringF(
-          'seaPenTemplateTerrain', `<${SeaPenTemplateChip.kTerrainFeature}>`,
-          `<${SeaPenTemplateChip.kTerrainColor}>`),
+          'seaPenTemplateGlowscapes',
+          `<${SeaPenTemplateChip.kGlowscapesLandscape}>`,
+          `<${SeaPenTemplateChip.kGlowscapesFeature}>`),
       preview: [{
         url:
-            'chrome://resources/ash/common/sea_pen/sea_pen_images/sea_pen_terrain.jpg',
+            'chrome://resources/ash/common/sea_pen/sea_pen_images/sea_pen_glowscapes.jpg',
       }],
       options: new Map([
         [
-          SeaPenTemplateChip.kTerrainFeature,
+          SeaPenTemplateChip.kGlowscapesLandscape,
           [
             {
-              value: SeaPenTemplateOption.kTerrainFeatureSaltLake,
+              value: SeaPenTemplateOption.kGlowscapesLandscapeValley,
+              translation: loadTimeData.getString(
+                  'seaPenOptionGlowscapesLandscapeValley'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/valley.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kGlowscapesLandscapeHotSpring,
+              translation: loadTimeData.getString(
+                  'seaPenOptionGlowscapesLandscapeHotSpring'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/hot_spring.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kGlowscapesLandscapeMeadow,
+              translation: loadTimeData.getString(
+                  'seaPenOptionGlowscapesLandscapeMeadow'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/meadow.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kGlowscapesLandscapeCoralReef,
+              translation: loadTimeData.getString(
+                  'seaPenOptionGlowscapesLandscapeCoralReef'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/coral_reef.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kGlowscapesLandscapePond,
               translation:
-                  loadTimeData.getString('seaPenOptionTerrainFeatureSaltLake'),
+                  loadTimeData.getString('seaPenOptionGlowscapesLandscapePond'),
               previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/salt_lake.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureRiver,
-              translation:
-                  loadTimeData.getString('seaPenOptionTerrainFeatureRiver'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/glacial_river.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureNorthernLights,
-              translation: loadTimeData.getString(
-                  'seaPenOptionTerrainFeatureNorthernLights'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/northern_lights.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureSandDunes,
-              translation:
-                  loadTimeData.getString('seaPenOptionTerrainFeatureSandDunes'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/sand_dune.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureClayHills,
-              translation:
-                  loadTimeData.getString('seaPenOptionTerrainFeatureClayHills'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/clay_hills.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureSandyLagoon,
-              translation: loadTimeData.getString(
-                  'seaPenOptionTerrainFeatureSandyLagoon'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/sandy_lagoon.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureMountains,
-              translation:
-                  loadTimeData.getString('seaPenOptionTerrainFeatureMountains'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/mountain.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureBioluminescentBeach,
-              translation: loadTimeData.getString(
-                  'seaPenOptionTerrainFeatureBioluminescentBeach'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/bioluminescent_beach.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureDifferentPlanet,
-              translation: loadTimeData.getString(
-                  'seaPenOptionTerrainFeatureDifferentPlanet'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/different_planet.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureTreeTunnel,
-              translation: loadTimeData.getString(
-                  'seaPenOptionTerrainFeatureTreeTunnel'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/tree_tunnel.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureBaobabTrees,
-              translation: loadTimeData.getString(
-                  'seaPenOptionTerrainFeatureBaobabTrees'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/baobab_trees.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureMistyForest,
-              translation: loadTimeData.getString(
-                  'seaPenOptionTerrainFeatureMistyForest'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/misty_forest.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainFeatureCactusForest,
-              translation: loadTimeData.getString(
-                  'seaPenOptionTerrainFeatureCactusForest'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/cactus_forest.jpg',
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/pond.jpg',
             },
           ],
         ],
         [
-          SeaPenTemplateChip.kTerrainColor,
+          SeaPenTemplateChip.kGlowscapesFeature,
           [
             {
-              value: SeaPenTemplateOption.kTerrainColorPink,
+              value: SeaPenTemplateOption.kGlowscapesFeatureRiver,
               translation:
-                  loadTimeData.getString('seaPenOptionTerrainColorPink'),
+                  loadTimeData.getString('seaPenOptionGlowscapesFeatureRiver'),
               previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/pink.jpg',
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/river.jpg',
             },
             {
-              value: SeaPenTemplateOption.kTerrainColorCyan,
-              translation:
-                  loadTimeData.getString('seaPenOptionTerrainColorCyan'),
+              value: SeaPenTemplateOption.kGlowscapesFeatureSaltRock,
+              translation: loadTimeData.getString(
+                  'seaPenOptionGlowscapesFeatureSaltRock'),
               previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/cyan.jpg',
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/salt_rock.jpg',
             },
             {
-              value: SeaPenTemplateOption.kTerrainColorWhite,
+              value: SeaPenTemplateOption.kGlowscapesFeatureFlower,
               translation:
-                  loadTimeData.getString('seaPenOptionTerrainColorWhite'),
+                  loadTimeData.getString('seaPenOptionGlowscapesFeatureFlower'),
               previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/white.jpg',
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/flower.jpg',
             },
             {
-              value: SeaPenTemplateOption.kTerrainColorPurple,
-              translation:
-                  loadTimeData.getString('seaPenOptionTerrainColorPurple'),
+              value: SeaPenTemplateOption.kGlowscapesFeatureWalkway,
+              translation: loadTimeData.getString(
+                  'seaPenOptionGlowscapesFeatureWalkway'),
               previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/purple.jpg',
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/walkway.jpg',
             },
             {
-              value: SeaPenTemplateOption.kTerrainColorBlue,
+              value: SeaPenTemplateOption.kGlowscapesFeaturePool,
               translation:
-                  loadTimeData.getString('seaPenOptionTerrainColorBlue'),
+                  loadTimeData.getString('seaPenOptionGlowscapesFeaturePool'),
               previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/blue.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainColorYellow,
-              translation:
-                  loadTimeData.getString('seaPenOptionTerrainColorYellow'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/yellow.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainColorMaroonPink,
-              translation:
-                  loadTimeData.getString('seaPenOptionTerrainColorMaroonPink'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/maroon_pink.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainColorBluePurple,
-              translation:
-                  loadTimeData.getString('seaPenOptionTerrainColorBluePurple'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/blue_purple.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainColorPinkYellow,
-              translation:
-                  loadTimeData.getString('seaPenOptionTerrainColorPinkYellow'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/pink_yellow.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kTerrainColorBluePink,
-              translation:
-                  loadTimeData.getString('seaPenOptionTerrainColorBluePink'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/blue_pink.jpg',
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/pool.jpg',
             },
           ],
         ],
@@ -276,13 +186,6 @@
               previewUrl:
                   'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/palace.jpg',
             },
-            {
-              value: SeaPenTemplateOption.kDreamscapesObjectChair,
-              translation:
-                  loadTimeData.getString('seaPenOptionDreamscapesObjectChair'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/chair.jpg',
-            },
           ],
         ],
         [
@@ -501,94 +404,184 @@
       ]),
     },
     {
-      id: SeaPenTemplateId.kGlowscapes,
-      title: loadTimeData.getString('seaPenTemplateTitleGlowscapes'),
+      id: SeaPenTemplateId.kTerrain,
+      title: loadTimeData.getString('seaPenTemplateTitleTerrain'),
       text: loadTimeData.getStringF(
-          'seaPenTemplateGlowscapes',
-          `<${SeaPenTemplateChip.kGlowscapesLandscape}>`,
-          `<${SeaPenTemplateChip.kGlowscapesFeature}>`),
+          'seaPenTemplateTerrain', `<${SeaPenTemplateChip.kTerrainFeature}>`,
+          `<${SeaPenTemplateChip.kTerrainColor}>`),
       preview: [{
         url:
-            'chrome://resources/ash/common/sea_pen/sea_pen_images/sea_pen_glowscapes.jpg',
+            'chrome://resources/ash/common/sea_pen/sea_pen_images/sea_pen_terrain.jpg',
       }],
       options: new Map([
         [
-          SeaPenTemplateChip.kGlowscapesLandscape,
+          SeaPenTemplateChip.kTerrainFeature,
           [
             {
-              value: SeaPenTemplateOption.kGlowscapesLandscapeValley,
-              translation: loadTimeData.getString(
-                  'seaPenOptionGlowscapesLandscapeValley'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/valley.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kGlowscapesLandscapeHotSpring,
-              translation: loadTimeData.getString(
-                  'seaPenOptionGlowscapesLandscapeHotSpring'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/hot_spring.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kGlowscapesLandscapeMeadow,
-              translation: loadTimeData.getString(
-                  'seaPenOptionGlowscapesLandscapeMeadow'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/meadow.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kGlowscapesLandscapeCoralReef,
-              translation: loadTimeData.getString(
-                  'seaPenOptionGlowscapesLandscapeCoralReef'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/coral_reef.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kGlowscapesLandscapePond,
+              value: SeaPenTemplateOption.kTerrainFeatureSaltLake,
               translation:
-                  loadTimeData.getString('seaPenOptionGlowscapesLandscapePond'),
+                  loadTimeData.getString('seaPenOptionTerrainFeatureSaltLake'),
               previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/pond.jpg',
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/salt_lake.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureRiver,
+              translation:
+                  loadTimeData.getString('seaPenOptionTerrainFeatureRiver'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/glacial_river.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureNorthernLights,
+              translation: loadTimeData.getString(
+                  'seaPenOptionTerrainFeatureNorthernLights'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/northern_lights.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureSandDunes,
+              translation:
+                  loadTimeData.getString('seaPenOptionTerrainFeatureSandDunes'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/sand_dune.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureClayHills,
+              translation:
+                  loadTimeData.getString('seaPenOptionTerrainFeatureClayHills'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/clay_hills.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureSandyLagoon,
+              translation: loadTimeData.getString(
+                  'seaPenOptionTerrainFeatureSandyLagoon'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/sandy_lagoon.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureMountains,
+              translation:
+                  loadTimeData.getString('seaPenOptionTerrainFeatureMountains'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/mountain.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureBioluminescentBeach,
+              translation: loadTimeData.getString(
+                  'seaPenOptionTerrainFeatureBioluminescentBeach'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/bioluminescent_beach.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureDifferentPlanet,
+              translation: loadTimeData.getString(
+                  'seaPenOptionTerrainFeatureDifferentPlanet'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/different_planet.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureTreeTunnel,
+              translation: loadTimeData.getString(
+                  'seaPenOptionTerrainFeatureTreeTunnel'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/tree_tunnel.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureBaobabTrees,
+              translation: loadTimeData.getString(
+                  'seaPenOptionTerrainFeatureBaobabTrees'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/baobab_trees.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureMistyForest,
+              translation: loadTimeData.getString(
+                  'seaPenOptionTerrainFeatureMistyForest'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/misty_forest.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureCactusForest,
+              translation: loadTimeData.getString(
+                  'seaPenOptionTerrainFeatureCactusForest'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/cactus_forest.jpg',
             },
           ],
         ],
         [
-          SeaPenTemplateChip.kGlowscapesFeature,
+          SeaPenTemplateChip.kTerrainColor,
           [
             {
-              value: SeaPenTemplateOption.kGlowscapesFeatureRiver,
+              value: SeaPenTemplateOption.kTerrainColorPink,
               translation:
-                  loadTimeData.getString('seaPenOptionGlowscapesFeatureRiver'),
+                  loadTimeData.getString('seaPenOptionTerrainColorPink'),
               previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/river.jpg',
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/pink.jpg',
             },
             {
-              value: SeaPenTemplateOption.kGlowscapesFeatureSaltRock,
-              translation: loadTimeData.getString(
-                  'seaPenOptionGlowscapesFeatureSaltRock'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/salt_rock.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kGlowscapesFeatureFlower,
+              value: SeaPenTemplateOption.kTerrainColorCyan,
               translation:
-                  loadTimeData.getString('seaPenOptionGlowscapesFeatureFlower'),
+                  loadTimeData.getString('seaPenOptionTerrainColorCyan'),
               previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/flower.jpg',
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/cyan.jpg',
             },
             {
-              value: SeaPenTemplateOption.kGlowscapesFeatureWalkway,
-              translation: loadTimeData.getString(
-                  'seaPenOptionGlowscapesFeatureWalkway'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/walkway.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kGlowscapesFeaturePool,
+              value: SeaPenTemplateOption.kTerrainColorWhite,
               translation:
-                  loadTimeData.getString('seaPenOptionGlowscapesFeaturePool'),
+                  loadTimeData.getString('seaPenOptionTerrainColorWhite'),
               previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/pool.jpg',
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/white.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorPurple,
+              translation:
+                  loadTimeData.getString('seaPenOptionTerrainColorPurple'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/purple.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorBlue,
+              translation:
+                  loadTimeData.getString('seaPenOptionTerrainColorBlue'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/blue.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorYellow,
+              translation:
+                  loadTimeData.getString('seaPenOptionTerrainColorYellow'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/yellow.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorMaroonPink,
+              translation:
+                  loadTimeData.getString('seaPenOptionTerrainColorMaroonPink'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/maroon_pink.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorBluePurple,
+              translation:
+                  loadTimeData.getString('seaPenOptionTerrainColorBluePurple'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/blue_purple.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorPinkYellow,
+              translation:
+                  loadTimeData.getString('seaPenOptionTerrainColorPinkYellow'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/pink_yellow.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorBluePink,
+              translation:
+                  loadTimeData.getString('seaPenOptionTerrainColorBluePink'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/blue_pink.jpg',
             },
           ],
         ],
@@ -688,6 +681,230 @@
       ]),
     },
     {
+      id: SeaPenTemplateId.kArt,
+      title: loadTimeData.getString('seaPenTemplateTitleArt'),
+      text: loadTimeData.getStringF(
+          'seaPenTemplateArt', `<${SeaPenTemplateChip.kArtFeature}>`,
+          `<${SeaPenTemplateChip.kArtMovement}>`),
+      preview: [{
+        url:
+            'chrome://resources/ash/common/sea_pen/sea_pen_images/sea_pen_art.jpg',
+      }],
+      options: new Map([
+        [
+          SeaPenTemplateChip.kArtFeature,
+          [
+            {
+              value: SeaPenTemplateOption.kArtFeatureField,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureField'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/field_of_flowers.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureSwamp,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureSwamp'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/swamp.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureBeach,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureBeach'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/beach.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureMeadow,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureMeadow'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/meadow.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureForest,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureForest'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/forest.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureGlacier,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureGlacier'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/glacier.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureIsland,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureIsland'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/island.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureJungle,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureJungle'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/jungle.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureLake,
+              translation: loadTimeData.getString('seaPenOptionArtFeatureLake'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/lake.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureCliff,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureCliff'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/cliff.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureOcean,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureOcean'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/vast_ocean.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureRiver,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureRiver'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/river.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureDune,
+              translation: loadTimeData.getString('seaPenOptionArtFeatureDune'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/sand_dune.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureMountain,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureMountain'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/mountain.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureValley,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureValley'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/valley.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureWaterfall,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureWaterfall'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/waterfall.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureCanyon,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureCanyon'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/canyon.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureCityscape,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureCityscape'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/cityscape.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureVillage,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtFeatureVillage'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/mediterranean_village.jpg',
+            },
+          ],
+        ],
+        [
+          SeaPenTemplateChip.kArtMovement,
+          [
+            {
+              value: SeaPenTemplateOption.kArtMovementAvantGarde,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtMovementAvantGarde'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/avant-garde.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementRealist,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtMovementRealist'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/realist.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementExpressionist,
+              translation: loadTimeData.getString(
+                  'seaPenOptionArtMovementExpressionist'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/expressionist.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementImpressionist,
+              translation: loadTimeData.getString(
+                  'seaPenOptionArtMovementImpressionist'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/impressionist.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementPostImpressionist,
+              translation: loadTimeData.getString(
+                  'seaPenOptionArtMovementPostImpressionist'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/post-impressionist.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementArtNouveau,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtMovementArtNouveau'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/art_nouveau.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementBauhaus,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtMovementBauhaus'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/bauhaus.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementWatercolor,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtMovementWatercolor'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/watercolor.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementAbstract,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtMovementAbstract'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/abstract.jpg',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementModernArt,
+              translation:
+                  loadTimeData.getString('seaPenOptionArtMovementModernArt'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/modern_art.jpg',
+            },
+          ],
+        ],
+      ]),
+    },
+    {
       id: SeaPenTemplateId.kLetters,
       title: loadTimeData.getString('seaPenTemplateTitleLetters'),
       text: loadTimeData.getStringF(
@@ -1496,230 +1713,6 @@
       ]),
     },
     {
-      id: SeaPenTemplateId.kArt,
-      title: loadTimeData.getString('seaPenTemplateTitleArt'),
-      text: loadTimeData.getStringF(
-          'seaPenTemplateArt', `<${SeaPenTemplateChip.kArtFeature}>`,
-          `<${SeaPenTemplateChip.kArtMovement}>`),
-      preview: [{
-        url:
-            'chrome://resources/ash/common/sea_pen/sea_pen_images/sea_pen_art.jpg',
-      }],
-      options: new Map([
-        [
-          SeaPenTemplateChip.kArtFeature,
-          [
-            {
-              value: SeaPenTemplateOption.kArtFeatureField,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureField'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/field_of_flowers.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureSwamp,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureSwamp'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/swamp.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureBeach,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureBeach'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/beach.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureMeadow,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureMeadow'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/meadow.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureForest,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureForest'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/forest.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureGlacier,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureGlacier'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/glacier.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureIsland,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureIsland'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/island.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureJungle,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureJungle'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/jungle.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureLake,
-              translation: loadTimeData.getString('seaPenOptionArtFeatureLake'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/lake.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureCliff,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureCliff'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/cliff.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureOcean,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureOcean'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/vast_ocean.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureRiver,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureRiver'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/river.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureDune,
-              translation: loadTimeData.getString('seaPenOptionArtFeatureDune'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/sand_dune.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureMountain,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureMountain'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/mountain.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureValley,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureValley'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/valley.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureWaterfall,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureWaterfall'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/waterfall.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureCanyon,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureCanyon'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/canyon.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureCityscape,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureCityscape'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/cityscape.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtFeatureVillage,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtFeatureVillage'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/mediterranean_village.jpg',
-            },
-          ],
-        ],
-        [
-          SeaPenTemplateChip.kArtMovement,
-          [
-            {
-              value: SeaPenTemplateOption.kArtMovementAvantGarde,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtMovementAvantGarde'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/avant-garde.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtMovementRealist,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtMovementRealist'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/realist.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtMovementExpressionist,
-              translation: loadTimeData.getString(
-                  'seaPenOptionArtMovementExpressionist'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/expressionist.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtMovementImpressionist,
-              translation: loadTimeData.getString(
-                  'seaPenOptionArtMovementImpressionist'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/impressionist.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtMovementPostImpressionist,
-              translation: loadTimeData.getString(
-                  'seaPenOptionArtMovementPostImpressionist'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/post-impressionist.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtMovementArtNouveau,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtMovementArtNouveau'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/art_nouveau.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtMovementBauhaus,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtMovementBauhaus'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/bauhaus.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtMovementWatercolor,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtMovementWatercolor'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/watercolor.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtMovementAbstract,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtMovementAbstract'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/abstract.jpg',
-            },
-            {
-              value: SeaPenTemplateOption.kArtMovementModernArt,
-              translation:
-                  loadTimeData.getString('seaPenOptionArtMovementModernArt'),
-              previewUrl:
-                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/modern_art.jpg',
-            },
-          ],
-        ],
-      ]),
-    },
-    {
       id: SeaPenTemplateId.kFlower,
       title: loadTimeData.getString('seaPenTemplateTitleFlower'),
       text: loadTimeData.getStringF(
@@ -2684,6 +2677,13 @@
               previewUrl:
                   'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/tower.jpg',
             },
+            {
+              value: SeaPenTemplateOption.kVcBackgroundDreamscapesObjectChair,
+              translation: loadTimeData.getString(
+                  'seaPenOptionVcBackgroundDreamscapesObjectChair'),
+              previewUrl:
+                  'https://www.gstatic.com/chromecast/home/chromeos/sea_pen/chair.jpg',
+            },
           ],
         ],
         [
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_images_element.html b/ash/webui/common/resources/sea_pen/sea_pen_images_element.html
index 0986eb6..ab64da7 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_images_element.html
+++ b/ash/webui/common/resources/sea_pen/sea_pen_images_element.html
@@ -135,7 +135,7 @@
 
   .error-message {
     max-width: 316px;
-    top: -12px;
+    top: -9px;
   }
 
 
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_images_element.ts b/ash/webui/common/resources/sea_pen/sea_pen_images_element.ts
index a29678a..1df6bfa3 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_images_element.ts
+++ b/ash/webui/common/resources/sea_pen/sea_pen_images_element.ts
@@ -140,8 +140,11 @@
     switch (statusCode) {
       case MantaStatusCode.kNoInternetConnection:
         return 'personalization-shared-illo:network_error';
-      default:
+      case MantaStatusCode.kPerUserQuotaExceeded:
+      case MantaStatusCode.kResourceExhausted:
         return 'personalization-shared-illo:resource_error';
+      default:
+        return 'personalization-shared-illo:generic_error';
     }
   }
 
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_recent_wallpapers_element.html b/ash/webui/common/resources/sea_pen/sea_pen_recent_wallpapers_element.html
index 4962e69..46aed5da 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_recent_wallpapers_element.html
+++ b/ash/webui/common/resources/sea_pen/sea_pen_recent_wallpapers_element.html
@@ -90,6 +90,12 @@
     right: 2px;
   }
 
+  .menu-icon-button:focus-visible:focus {
+    box-shadow: none;
+    outline: 2px solid var(--cros-sys-focus_ring);
+    outline-offset: 1px;
+  }
+
   .dropdown-item > iron-icon {
     --iron-icon-fill-color: var(--cros-sys-on_surface);
     --iron-icon-height: 20px;
diff --git a/ash/webui/shimless_rma/resources/reboot_page.html b/ash/webui/shimless_rma/resources/reboot_page.html
index da90c99..01a0680 100644
--- a/ash/webui/shimless_rma/resources/reboot_page.html
+++ b/ash/webui/shimless_rma/resources/reboot_page.html
@@ -3,8 +3,8 @@
 
 <base-page>
   <div slot="left-pane">
-    <h1 tabindex="-1">[[getPageTitle(errorCode)]]</h1>
-    <div class="instructions">
+    <h1 id="title" tabindex="-1">[[getPageTitle(errorCode)]]</h1>
+    <div id="instructions" class="instructions">
       [[getPageInstructions(errorCode)]]
     </div>
   </div>
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 9b6b971..34ebf66 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -3294,6 +3294,7 @@
     ignored_items.insert(dragged_item);
   }
   PositionWindows(animate, ignored_items);
+  UpdateNoWindowsWidget(empty(), animate, /*is_continuous_enter=*/false);
 }
 
 void OverviewGrid::OnSkipButtonPressed() {
diff --git a/ash/wm/overview/overview_item_view.cc b/ash/wm/overview/overview_item_view.cc
index d5d836f..673676be 100644
--- a/ash/wm/overview/overview_item_view.cc
+++ b/ash/wm/overview/overview_item_view.cc
@@ -189,8 +189,7 @@
   // needed for the overview group item.
   if (SnapGroupController* snap_group_controller = SnapGroupController::Get()) {
     const aura::Window* window = overview_item_->GetWindow();
-    if (SnapGroup* snap_group =
-            snap_group_controller->GetSnapGroupForGivenWindow(window)) {
+    if (snap_group_controller->GetSnapGroupForGivenWindow(window)) {
       SetRoundedCornersRadius(window_util::GetMiniWindowRoundedCorners(
           window, /*include_header_rounding=*/true));
 
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index a51a2343..6ada017 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -257,6 +257,12 @@
                                    OverviewTransition::kEnter);
   }
 
+  // TODO(http://b/326091611): In the case of dragging a window from the shelf
+  // with one window total, this will create the no windows widget. Then, we
+  // will be notified the drag has started and a drop target will be added,
+  // hiding the no windows widget. This all happens before the frame is
+  // presented so it looks ok from the users perspective, but we should avoid
+  // creating it in the first place.
   const bool is_continuous_enter =
       enter_exit_overview_type_ ==
       OverviewEnterExitType::kContinuousAnimationEnterOnScrollUpdate;
diff --git a/ash/wm/overview/scoped_overview_wallpaper_clipper.cc b/ash/wm/overview/scoped_overview_wallpaper_clipper.cc
index f1bfb49..21e1f61 100644
--- a/ash/wm/overview/scoped_overview_wallpaper_clipper.cc
+++ b/ash/wm/overview/scoped_overview_wallpaper_clipper.cc
@@ -82,8 +82,7 @@
           [](WallpaperWidgetController* wallpaper_widget_controller) {
             // `WallpaperWidgetController` owns the wallpaper view layer and
             // wallpaper underlay layer, so it's guaranteed to outlive it.
-            if (auto* wallpaper_underlay_layer =
-                    wallpaper_widget_controller->wallpaper_underlay_layer()) {
+            if (wallpaper_widget_controller->wallpaper_underlay_layer()) {
               wallpaper_widget_controller->wallpaper_underlay_layer()
                   ->SetVisible(false);
             }
diff --git a/ash/wm/resize_shadow_controller.cc b/ash/wm/resize_shadow_controller.cc
index f8a1815..81744d72 100644
--- a/ash/wm/resize_shadow_controller.cc
+++ b/ash/wm/resize_shadow_controller.cc
@@ -10,6 +10,7 @@
 #include "ash/shell.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/resize_shadow.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/ui/base/window_state_type.h"
 #include "chromeos/ui/frame/frame_utils.h"
 #include "ui/aura/client/aura_constants.h"
@@ -164,7 +165,8 @@
       window->GetProperty(ash::kResizeShadowTypeKey);
   const int window_corner_radius =
       window->GetProperty(aura::client::kWindowCornerRadiusKey);
-  const bool has_rounded_window = window_corner_radius > 0;
+  const bool has_rounded_window =
+      chromeos::features::IsRoundedWindowsEnabled() && window_corner_radius > 0;
 
   // If the `window` has a resize shadow with the requested type and the shadow
   // is configured for a rounded window, no need to recreate it.
diff --git a/ash/wm/snap_group/snap_group_controller.cc b/ash/wm/snap_group/snap_group_controller.cc
index c47a0951..d2c2817c 100644
--- a/ash/wm/snap_group/snap_group_controller.cc
+++ b/ash/wm/snap_group/snap_group_controller.cc
@@ -127,7 +127,12 @@
 bool SnapGroupController::OnSnappingWindow(
     aura::Window* to_be_snapped_window,
     WindowSnapActionSource snap_action_source) {
-  if (display::Screen::GetScreen()->InTabletMode()) {
+  // Early return when `snap_action_source` originates from
+  // `kDragOrSelectOverviewWindowToSnap` to avoid snap-to-replace within another
+  // snap group in Overview.
+  if (display::Screen::GetScreen()->InTabletMode() ||
+      snap_action_source ==
+          WindowSnapActionSource::kDragOrSelectOverviewWindowToSnap) {
     return false;
   }
 
diff --git a/ash/wm/snap_group/snap_group_unittest.cc b/ash/wm/snap_group/snap_group_unittest.cc
index 5df7bf8..a92ed35 100644
--- a/ash/wm/snap_group/snap_group_unittest.cc
+++ b/ash/wm/snap_group/snap_group_unittest.cc
@@ -4654,6 +4654,39 @@
       snap_group_controller->AreWindowsInSnapGroup(w4.get(), w3.get()));
 }
 
+// Tests that when dragging another window to snap in Overview with the
+// existence of snap group. The to-be-snapped window will not replace the window
+// in the snap group. See http://b/333603509 for more details.
+TEST_F(SnapGroupTest, DoNotSnapToReplaceSnapGroupInOverview) {
+  std::unique_ptr<aura::Window> w0(
+      CreateAppWindow(gfx::Rect(10, 10, 200, 100)));
+
+  std::unique_ptr<aura::Window> w1(CreateAppWindow());
+  std::unique_ptr<aura::Window> w2(CreateAppWindow());
+  SnapTwoTestWindows(w1.get(), w2.get());
+  ASSERT_FALSE(split_view_controller()->InSplitViewMode());
+  SnapGroupController* snap_group_controller = SnapGroupController::Get();
+  ASSERT_TRUE(snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
+
+  OverviewController* overview_controller = OverviewController::Get();
+  overview_controller->StartOverview(OverviewStartAction::kOverviewButton);
+  auto* overview_item0 = GetOverviewItemForWindow(w0.get());
+  auto* event_generator = GetEventGenerator();
+  event_generator->set_current_screen_location(
+      gfx::ToRoundedPoint(overview_item0->target_bounds().CenterPoint()));
+  event_generator->PressLeftButton();
+  event_generator->MoveMouseTo(gfx::Point(0, 0));
+  event_generator->ReleaseLeftButton();
+  EXPECT_EQ(WindowState::Get(w0.get())->GetStateType(),
+            chromeos::WindowStateType::kPrimarySnapped);
+
+  EXPECT_TRUE(snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
+  EXPECT_FALSE(
+      snap_group_controller->AreWindowsInSnapGroup(w0.get(), w1.get()));
+  EXPECT_FALSE(
+      snap_group_controller->AreWindowsInSnapGroup(w0.get(), w2.get()));
+}
+
 // -----------------------------------------------------------------------------
 // SnapGroupHistogramTest:
 
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index ad88617..4a6c187b 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -2548,8 +2548,7 @@
     // `SplitViewOverviewSession` is for clamshell only.
     RootWindowController* root_window_controller =
         RootWindowController::ForWindow(root_window_);
-    if (SplitViewOverviewSession* split_view_overview_session =
-            root_window_controller->split_view_overview_session()) {
+    if (root_window_controller->split_view_overview_session()) {
       root_window_controller->EndSplitViewOverviewSession(
           SplitViewOverviewSessionExitPoint::kTabletConversion);
     }
diff --git a/ash/wm/window_restore/pine_contents_view.cc b/ash/wm/window_restore/pine_contents_view.cc
index 3f503d1c..f5c3bda 100644
--- a/ash/wm/window_restore/pine_contents_view.cc
+++ b/ash/wm/window_restore/pine_contents_view.cc
@@ -47,9 +47,6 @@
 namespace {
 
 // TODO(http://b/322359738): Localize all these strings.
-// TODO(http://b/322360273): Match specs.
-// TODO(http://b/328459389): Update `SetFontList()` to use
-// `ash::TypographyProvider`.
 
 constexpr int kButtonContainerChildSpacing = 10;
 // The margins for the container view which houses the cancel and restore
@@ -59,8 +56,6 @@
 constexpr int kContentsChildSpacing = 16;
 constexpr gfx::Insets kContentsInsets(20);
 constexpr int kContentsRounding = 20;
-constexpr int kContentsTitleFontSize = 22;
-constexpr int kContentsDescriptionFontSize = 14;
 constexpr int kLeftContentsChildSpacing = 6;
 constexpr int kSettingsIconSize = 24;
 
@@ -109,21 +104,23 @@
               // Title.
               views::Builder<views::Label>()
                   .SetEnabledColorId(cros_tokens::kCrosSysOnSurface)
-                  .SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL,
-                                             kContentsTitleFontSize,
-                                             gfx::Font::Weight::BOLD))
                   .SetHorizontalAlignment(gfx::ALIGN_LEFT)
                   .SetMultiLine(true)
-                  .SetText(l10n_util::GetStringUTF16(title_message_id)),
+                  .SetText(l10n_util::GetStringUTF16(title_message_id))
+                  .CustomConfigure(base::BindOnce([](views::Label* label) {
+                    TypographyProvider::Get()->StyleLabel(
+                        TypographyToken::kCrosDisplay7, *label);
+                  })),
               // Description.
               views::Builder<views::Label>()
                   .SetEnabledColorId(cros_tokens::kCrosSysOnSurface)
-                  .SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL,
-                                             kContentsDescriptionFontSize,
-                                             gfx::Font::Weight::NORMAL))
                   .SetHorizontalAlignment(gfx::ALIGN_LEFT)
                   .SetMultiLine(true)
-                  .SetText(l10n_util::GetStringUTF16(description_message_id)),
+                  .SetText(l10n_util::GetStringUTF16(description_message_id))
+                  .CustomConfigure(base::BindOnce([](views::Label* label) {
+                    TypographyProvider::Get()->StyleLabel(
+                        TypographyToken::kCrosBody1, *label);
+                  })),
               // This box layout view is the container for the "No thanks" and
               // "Restore" pill buttons.
               views::Builder<views::BoxLayoutView>()
diff --git a/ash/wm/window_restore/pine_item_view.cc b/ash/wm/window_restore/pine_item_view.cc
index 1af912d..2187335 100644
--- a/ash/wm/window_restore/pine_item_view.cc
+++ b/ash/wm/window_restore/pine_item_view.cc
@@ -19,10 +19,10 @@
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_skia_operations.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/separator.h"
+#include "ui/views/highlight_border.h"
 
 namespace ash {
 
@@ -96,11 +96,12 @@
             .AddChildren(
                 views::Builder<views::Label>()
                     .SetEnabledColorId(pine::kPineItemTextColorId)
-                    .SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL,
-                                               pine::kItemTitleFontSize,
-                                               gfx::Font::Weight::BOLD))
                     .SetHorizontalAlignment(gfx::ALIGN_LEFT)
-                    .SetText(base::UTF8ToUTF16(title)),
+                    .SetText(base::UTF8ToUTF16(title))
+                    .CustomConfigure(base::BindOnce([](views::Label* label) {
+                      TypographyProvider::Get()->StyleLabel(
+                          TypographyToken::kCrosButton2, *label);
+                    })),
                 views::Builder<views::BoxLayoutView>()
                     .CopyAddressTo(&favicon_container_view_)
                     .SetID(pine::kFaviconContainerViewID)
@@ -157,11 +158,9 @@
 
     views::Builder<views::ImageView> builder;
     builder
-        // TODO(b/322360273): The border is temporary for more
-        // contrast until specs are ready.
-        .SetBorder(views::CreateRoundedRectBorder(
-            /*thickness=*/1,
-            /*corner_radius=*/kFaviconPreferredSize.width(), SK_ColorBLACK))
+        .SetBorder(std::make_unique<views::HighlightBorder>(
+            /*corner_radius=*/kFaviconPreferredSize.width(),
+            views::HighlightBorder::Type::kHighlightBorderNoShadow))
         .SetImageSize(kFaviconPreferredSize);
 
     if (inside_screenshot_) {
diff --git a/ash/wm/window_restore/pine_items_overflow_view.cc b/ash/wm/window_restore/pine_items_overflow_view.cc
index 52a5cfd45d..fd5c498 100644
--- a/ash/wm/window_restore/pine_items_overflow_view.cc
+++ b/ash/wm/window_restore/pine_items_overflow_view.cc
@@ -136,14 +136,13 @@
   AddChildView(views::Builder<views::Label>()
                    .CopyAddressTo(&remaining_windows_label)
                    .SetEnabledColorId(pine::kPineItemTextColorId)
-                   .SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL,
-                                              pine::kItemTitleFontSize,
-                                              gfx::Font::Weight::BOLD))
                    .SetHorizontalAlignment(gfx::ALIGN_LEFT)
                    .SetText(l10n_util::GetPluralStringFUTF16(
                        IDS_ASH_FOREST_WINDOW_OVERFLOW_COUNT,
                        num_elements - pine::kOverflowMinThreshold))
                    .Build());
+  TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton2,
+                                        *remaining_windows_label);
   SetFlexForView(remaining_windows_label, 1);
 }
 
diff --git a/base/android/java/src/org/chromium/base/PackageManagerUtils.java b/base/android/java/src/org/chromium/base/PackageManagerUtils.java
index 91d814c0..a467d72a 100644
--- a/base/android/java/src/org/chromium/base/PackageManagerUtils.java
+++ b/base/android/java/src/org/chromium/base/PackageManagerUtils.java
@@ -15,6 +15,8 @@
 
 /** This class provides Android PackageManager related utility methods. */
 public class PackageManagerUtils {
+    public static final String XR_IMMERSIVE_FEATURE_NAME = "android.software.xr.immersive";
+
     private static final String TAG = "PackageManagerUtils";
 
     // This is the intent Android uses internally to detect browser apps.
@@ -84,6 +86,12 @@
         return canResolveActivity(intent, 0);
     }
 
+    /** Check if the system has the given system feature available. */
+    public static boolean hasSystemFeature(String feature) {
+        PackageManager pm = ContextUtils.getApplicationContext().getPackageManager();
+        return pm.hasSystemFeature(feature);
+    }
+
     /**
      * @return Intent to query a list of installed home launchers.
      */
diff --git a/base/message_loop/message_pump.cc b/base/message_loop/message_pump.cc
index 48a5ee0..12891247 100644
--- a/base/message_loop/message_pump.cc
+++ b/base/message_loop/message_pump.cc
@@ -49,7 +49,7 @@
 MessagePump::~MessagePump() = default;
 
 bool MessagePump::HandleNestedNativeLoopWithApplicationTasks(
-    NativeLoopStatus allowed) {
+    bool application_tasks_desired) {
   return false;
 }
 
@@ -120,6 +120,7 @@
 #if BUILDFLAG(IS_WIN)
   g_explicit_high_resolution_timer_win =
       FeatureList::IsEnabled(kExplicitHighResolutionTimerWin);
+  MessagePumpWin::InitializeFeatures();
 #endif
 }
 
diff --git a/base/message_loop/message_pump.h b/base/message_loop/message_pump.h
index 141cb2d..aee9030c 100644
--- a/base/message_loop/message_pump.h
+++ b/base/message_loop/message_pump.h
@@ -277,17 +277,13 @@
                                          TimeTicks run_time,
                                          TimeTicks latest_time);
 
-  enum class NativeLoopStatus {
-    kOnEntry,
-    kOnExit,
-  };
-  // Requests the pump to handle either the likely imminent creation
-  // (`kOnEntry`) of a native nested loop, or its exiting (`kOnExit`). The pump
-  // should override and return `true` if it supports this call and has
-  // scheduled work in response. The default implementation returns `false` and
-  // does nothing.
+  // Requests the pump to handle either the likely imminent creation (`true`) or
+  // destruction (`false`) of a native nested loop in which application tasks
+  // are desired to be run. The pump should override and return `true` if it
+  // supports this call and has scheduled work in response. The default
+  // implementation returns `false` and does nothing.
   virtual bool HandleNestedNativeLoopWithApplicationTasks(
-      NativeLoopStatus allowed);
+      bool application_tasks_desired);
 };
 
 }  // namespace base
diff --git a/base/message_loop/message_pump_win.cc b/base/message_loop/message_pump_win.cc
index d5f85cf..941675b 100644
--- a/base/message_loop/message_pump_win.cc
+++ b/base/message_loop/message_pump_win.cc
@@ -4,7 +4,10 @@
 
 #include "base/message_loop/message_pump_win.h"
 
+#include <winbase.h>
+
 #include <algorithm>
+#include <atomic>
 #include <cstdint>
 #include <type_traits>
 
@@ -16,6 +19,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/task/task_features.h"
 #include "base/trace_event/base_tracing.h"
 #include "base/tracing_buildflags.h"
 
@@ -27,14 +31,6 @@
 
 namespace {
 
-enum MessageLoopProblems {
-  MESSAGE_POST_ERROR,
-  COMPLETION_POST_ERROR,
-  SET_TIMER_ERROR,
-  RECEIVED_WM_QUIT_ERROR,
-  MESSAGE_LOOP_PROBLEM_MAX,
-};
-
 // Returns the number of milliseconds before |next_task_time|, clamped between
 // zero and the biggest DWORD value (or INFINITE if |next_task_time.is_max()|).
 // Optionally, a recent value of Now() may be passed in to avoid resampling it.
@@ -56,6 +52,8 @@
   return saturated_cast<DWORD>(timeout_ms);
 }
 
+bool g_ui_pump_improvements_win = false;
+
 }  // namespace
 
 // Message sent to get an additional time slice for pumping (processing) another
@@ -68,6 +66,11 @@
 MessagePumpWin::MessagePumpWin() = default;
 MessagePumpWin::~MessagePumpWin() = default;
 
+// static
+void MessagePumpWin::InitializeFeatures() {
+  g_ui_pump_improvements_win = FeatureList::IsEnabled(kUIPumpImprovementsWin);
+}
+
 void MessagePumpWin::Run(Delegate* delegate) {
   DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
 
@@ -101,28 +104,37 @@
   // This is the only MessagePumpForUI method which can be called outside of
   // |bound_thread_|.
 
+  if (g_ui_pump_improvements_win &&
+      nested_state_ != NestedState::kNestedNativeLoopAnnounced) {
+    // The pump is running using `event_` as its chrome-side synchronization
+    // variable. In this case, no deduplication is done, since the event has its
+    // own state.
+    event_.Signal();
+    return;
+  }
+
   bool not_scheduled = false;
-  if (!work_scheduled_.compare_exchange_strong(not_scheduled, true,
-                                               std::memory_order_relaxed)) {
+  if (!native_msg_scheduled_.compare_exchange_strong(
+          not_scheduled, true, std::memory_order_relaxed)) {
     return;  // Someone else continued the pumping.
   }
 
-  // Make sure the MessagePump does some work for us.
   const BOOL ret = ::PostMessage(message_window_.hwnd(), kMsgHaveWork, 0, 0);
-  if (ret)
+  if (ret) {
     return;  // There was room in the Window Message queue.
+  }
 
   // We have failed to insert a have-work message, so there is a chance that we
-  // will starve tasks/timers while sitting in a nested run loop.  Nested
-  // loops only look at Windows Message queues, and don't look at *our* task
-  // queues, etc., so we might not get a time slice in such. :-(
+  // will starve tasks/timers while sitting in a nested run loop. Nested loops
+  // only look at Windows Message queues, and don't look at *our* task queues,
+  // etc., so we might not get a time slice in such. :-(
   // We could abort here, but the fear is that this failure mode is plausibly
   // common (queue is full, of about 2000 messages), so we'll do a near-graceful
   // recovery.  Nested loops are pretty transient (we think), so this will
   // probably be recoverable.
 
   // Clarify that we didn't really insert.
-  work_scheduled_.store(false, std::memory_order_relaxed);
+  native_msg_scheduled_.store(false, std::memory_order_relaxed);
   TRACE_EVENT_INSTANT0("base", "Chrome.MessageLoopProblem.MESSAGE_POST_ERROR",
                        TRACE_EVENT_SCOPE_THREAD);
 }
@@ -135,7 +147,8 @@
   // nothing to do as the loop is already running. When the loop becomes idle,
   // it will typically WaitForWork() in DoRunLoop() with the timeout provided by
   // DoWork(). The only alternative to this is entering a native nested loop
-  // (e.g. modal dialog) under a ScopedNestableTaskAllower, in which case
+  // (e.g. modal dialog) under a
+  // `ScopedAllowApplicationTasksInNativeNestedLoop`, in which case
   // HandleWorkMessage() will be invoked when the system picks up kMsgHaveWork
   // and it will ScheduleNativeTimer() if it's out of immediate work. However,
   // in that alternate scenario : it's possible for a Windows native work item
@@ -147,11 +160,28 @@
   // See MessageLoopTest.PostDelayedTaskFromSystemPump for an example.
   // TODO(gab): This could potentially be replaced by a ForegroundIdleProc hook
   // if Windows ends up being the only platform requiring ScheduleDelayedWork().
-  if (in_native_loop_ && !work_scheduled_.load(std::memory_order_relaxed)) {
+  if (nested_state_ != NestedState::kNone &&
+      !native_msg_scheduled_.load(std::memory_order_relaxed)) {
     ScheduleNativeTimer(next_work_info);
   }
 }
 
+bool MessagePumpForUI::HandleNestedNativeLoopWithApplicationTasks(
+    bool application_tasks_desired) {
+  if (application_tasks_desired) {
+    // It is here assumed that we will be in a native loop until either
+    // DoRunLoop() gets control back, or this is called with `false`, and thus
+    // must `use_windows_event_queue_for_synchronization_`. This is to prevent
+    // being unable to wake up for application tasks in the case of a nested
+    // loop.
+    nested_state_ = NestedState::kNestedNativeLoopAnnounced;
+    ScheduleWork();
+  } else {
+    nested_state_ = NestedState::kNone;
+  }
+  return true;
+}
+
 void MessagePumpForUI::AddObserver(Observer* observer) {
   DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
   observers_.AddObserver(observer);
@@ -205,15 +235,19 @@
     // work, then it is a good time to consider sleeping (waiting) for more
     // work.
 
-    in_native_loop_ = false;
+    nested_state_ = NestedState::kNone;
 
     bool more_work_is_plausible = ProcessNextWindowsMessage();
-    in_native_loop_ = false;
+    // We can end up in native loops which allow application tasks outside of
+    // DoWork() when Windows calls back a Win32 message window owned by some
+    // Chromium code.
+    nested_state_ = NestedState::kNone;
+
     if (run_state_->should_quit)
       break;
 
     Delegate::NextWorkInfo next_work_info = run_state_->delegate->DoWork();
-    in_native_loop_ = false;
+    nested_state_ = NestedState::kNone;
     more_work_is_plausible |= next_work_info.is_immediate();
     if (run_state_->should_quit)
       break;
@@ -230,9 +264,10 @@
       continue;
 
     more_work_is_plausible = run_state_->delegate->DoIdleWork();
-    // DoIdleWork() shouldn't end up in native nested loops and thus shouldn't
-    // have any chance of reinstalling a native timer.
-    DCHECK(!in_native_loop_);
+    // DoIdleWork() shouldn't end up in native nested loops, nor should it
+    // permit native nested loops, and thus shouldn't have any chance of
+    // reinstalling a native timer.
+    DCHECK_EQ(nested_state_, NestedState::kNone);
     DCHECK(!installed_native_timer_);
     if (run_state_->should_quit)
       break;
@@ -262,10 +297,27 @@
     // Tell the optimizer to retain these values to simplify analyzing hangs.
     base::debug::Alias(&delay);
     base::debug::Alias(&wait_flags);
-    DWORD result = MsgWaitForMultipleObjectsEx(0, nullptr, delay, QS_ALLINPUT,
-                                               wait_flags);
-
-    if (WAIT_OBJECT_0 == result) {
+    DWORD result;
+    bool awakened_for_task = false;
+    bool awakened_for_native = false;
+    if (g_ui_pump_improvements_win) {
+      HANDLE event_handle = event_.handle();
+      result = MsgWaitForMultipleObjectsEx(1, &event_handle, delay, QS_ALLINPUT,
+                                           wait_flags);
+      DPCHECK(WAIT_FAILED != result);
+      awakened_for_task = (result == WAIT_OBJECT_0);
+      awakened_for_native = (result == WAIT_OBJECT_0 + 1);
+    } else {
+      result = MsgWaitForMultipleObjectsEx(0, nullptr, delay, QS_ALLINPUT,
+                                           wait_flags);
+      DPCHECK(WAIT_FAILED != result);
+      // It can't be known whether the pump woke to process a task until the
+      // queue is peeked.
+      awakened_for_native = (result == WAIT_OBJECT_0);
+    }
+    if (awakened_for_task) {
+      return;
+    } else if (awakened_for_native) {
       // A WM_* message is available.
       // If a parent child relationship exists between windows across threads
       // then their thread inputs are implicitly attached.
@@ -310,24 +362,24 @@
                 ->set_wait_for_object_result(result);
           });
     }
-
-    DCHECK_NE(WAIT_FAILED, result) << GetLastError();
   }
 }
 
 void MessagePumpForUI::HandleWorkMessage() {
   DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
 
-  // The kMsgHaveWork message was consumed by a native loop, we must assume
-  // we're in one until DoRunLoop() gets control back.
-  in_native_loop_ = true;
+  if (nested_state_ != NestedState::kNestedNativeLoopAnnounced) {
+    // The kMsgHaveWork message was consumed by a native loop, we must assume
+    // we're in one until DoRunLoop() gets control back.
+    nested_state_ = NestedState::kNestedNativeLoopDetected;
+  }
 
   // If we are being called outside of the context of Run, then don't try to do
   // any work.  This could correspond to a MessageBox call or something of that
   // sort.
   if (!run_state_) {
     // Since we handled a kMsgHaveWork message, we must still update this flag.
-    work_scheduled_.store(false, std::memory_order_relaxed);
+    native_msg_scheduled_.store(false, std::memory_order_relaxed);
     return;
   }
 
@@ -380,7 +432,8 @@
 void MessagePumpForUI::ScheduleNativeTimer(
     Delegate::NextWorkInfo next_work_info) {
   DCHECK(!next_work_info.is_immediate());
-  DCHECK(in_native_loop_);
+  // Ensure we are nested in some way.
+  DCHECK_NE(nested_state_, NestedState::kNone);
 
   // Do not redundantly set the same native timer again if it was already set.
   // This can happen when a nested native loop goes idle with pending delayed
@@ -400,20 +453,21 @@
   // granularity. Instead we rely on MsgWaitForMultipleObjectsEx's
   // high-resolution timeout to sleep without timers in WaitForWork(). However,
   // when entering a nested native ::GetMessage() loop (e.g. native modal
-  // windows) under a ScopedNestableTaskAllower, we have to rely on a native
-  // timer when HandleWorkMessage() runs out of immediate work. Since
-  // ScopedNestableTaskAllower invokes ScheduleWork() : we are guaranteed that
-  // HandleWorkMessage() will be called after entering a nested native loop that
-  // should process application tasks. But once HandleWorkMessage() is out of
-  // immediate work, ::SetTimer() is used to guarantee we are invoked again
-  // should the next delayed task expire before the nested native loop ends. The
-  // native timer being unnecessary once we return to our DoRunLoop(), we
-  // ::KillTimer when it resumes (nested native loops should be rare so we're
-  // not worried about ::SetTimer<=>::KillTimer churn).
-  // TODO(gab): The long-standing legacy dependency on the behavior of
-  // ScopedNestableTaskAllower is unfortunate, would be nice to make this a
-  // MessagePump concept (instead of requiring impls to invoke ScheduleWork()
-  // one-way and no-op DoWork() the other way).
+  // windows) under a `ScopedAllowApplicationTasksInNativeNestedLoop`, we have
+  // to rely on a native timer when HandleWorkMessage() runs out of immediate
+  // work. Since `ScopedAllowApplicationTasksInNativeNestedLoop` invokes
+  // ScheduleWork() : we are guaranteed that HandleWorkMessage() will be called
+  // after entering a nested native loop that should process application
+  // tasks. But once HandleWorkMessage() is out of immediate work, ::SetTimer()
+  // is used to guarantee we are invoked again should the next delayed task
+  // expire before the nested native loop ends. The native timer being
+  // unnecessary once we return to our DoRunLoop(), we ::KillTimer when it
+  // resumes (nested native loops should be rare so we're not worried about
+  // ::SetTimer<=>::KillTimer churn).  TODO(gab): The long-standing legacy
+  // dependency on the behavior of
+  // `ScopedAllowApplicationTasksInNativeNestedLoop` is unfortunate, would be
+  // nice to make this a MessagePump concept (instead of requiring impls to
+  // invoke ScheduleWork() one-way and no-op DoWork() the other way).
 
   UINT delay_msec = strict_cast<UINT>(GetSleepTimeoutMs(
       next_work_info.delayed_run_time, next_work_info.recent_now));
@@ -580,8 +634,8 @@
          msg.hwnd != message_window_.hwnd());
 
   // Since we discarded a kMsgHaveWork message, we must update the flag.
-  DCHECK(work_scheduled_.load(std::memory_order_relaxed));
-  work_scheduled_.store(false, std::memory_order_relaxed);
+  DCHECK(native_msg_scheduled_.load(std::memory_order_relaxed));
+  native_msg_scheduled_.store(false, std::memory_order_relaxed);
 
   // We don't need a special time slice if we didn't |have_message| to process.
   if (!have_message)
@@ -655,9 +709,9 @@
   // |bound_thread_|.
 
   bool not_scheduled = false;
-  if (!work_scheduled_.compare_exchange_strong(not_scheduled, true,
-                                               std::memory_order_relaxed)) {
-    return;  // Someone else continued the pumping.
+  if (!native_msg_scheduled_.compare_exchange_strong(
+          not_scheduled, true, std::memory_order_relaxed)) {
+    return;  // Work already scheduled.
   }
 
   // Make sure the MessagePump does some work for us.
@@ -669,7 +723,7 @@
 
   // See comment in MessagePumpForUI::ScheduleWork() for this error recovery.
 
-  work_scheduled_.store(
+  native_msg_scheduled_.store(
       false, std::memory_order_relaxed);  // Clarify that we didn't succeed.
   TRACE_EVENT_INSTANT0("base",
                        "Chrome.MessageLoopProblem.COMPLETION_POST_ERROR",
@@ -820,7 +874,7 @@
           reinterpret_cast<void*>(item.handler.get())) {
     // This is our internal completion.
     DCHECK(!item.bytes_transfered);
-    work_scheduled_.store(false, std::memory_order_relaxed);
+    native_msg_scheduled_.store(false, std::memory_order_relaxed);
     return true;
   }
   return false;
diff --git a/base/message_loop/message_pump_win.h b/base/message_loop/message_pump_win.h
index 7a36048..880b1ea 100644
--- a/base/message_loop/message_pump_win.h
+++ b/base/message_loop/message_pump_win.h
@@ -17,6 +17,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/message_loop/message_pump.h"
 #include "base/observer_list.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
 #include "base/win/message_window.h"
@@ -36,6 +37,8 @@
   void Run(Delegate* delegate) override;
   void Quit() override;
 
+  static void InitializeFeatures();
+
  protected:
   struct RunState {
     explicit RunState(Delegate* delegate_in) : delegate(delegate_in) {}
@@ -54,11 +57,12 @@
   // True iff:
   //   * MessagePumpForUI: there's a kMsgDoWork message pending in the Windows
   //     Message queue. i.e. when:
-  //      a. The pump is about to wakeup from idle.
+  //      a. The pump is about to wakeup from idle and kUIPumpImprovementsWin
+  //         is not enabled.
   //      b. The pump is about to enter a nested native loop and a
-  //         ScopedAllowApplicationTasksInNativeNestedLoop was instantiated to
+  //         `ScopedAllowApplicationTasksInNativeNestedLoop` was instantiated to
   //         allow application tasks to execute in that nested loop
-  //         (ScopedAllowApplicationTasksInNativeNestedLoop invokes
+  //         (`ScopedAllowApplicationTasksInNativeNestedLoop` invokes
   //         ScheduleWork()).
   //      c. While in a native (nested) loop : HandleWorkMessage() =>
   //         ProcessPumpReplacementMessage() invokes ScheduleWork() before
@@ -67,16 +71,16 @@
   //         nested loop. This is different from (b.) because we're not yet
   //         processing an application task at the current run level and
   //         therefore are expected to keep pumping application tasks without
-  //         necessitating a ScopedAllowApplicationTasksInNativeNestedLoop.
+  //         necessitating a `ScopedAllowApplicationTasksInNativeNestedLoop`.
   //
-  //   * MessagePumpforIO: there's a dummy IO completion item with |this| as an
+  //   * MessagePumpforIO: there's a dummy IO completion item with `this` as an
   //     lpCompletionKey in the queue which is about to wakeup
   //     WaitForIOCompletion(). MessagePumpForIO doesn't support nesting so
   //     this is simpler than MessagePumpForUI.
   //
   // Note that this should not be used for memory ordering. It is accessed via
   // `memory_order_relaxed` in all cases.
-  std::atomic_bool work_scheduled_{false};
+  std::atomic_bool native_msg_scheduled_{false};
 
   raw_ptr<RunState> run_state_ = nullptr;
 
@@ -138,6 +142,8 @@
   void ScheduleWork() override;
   void ScheduleDelayedWork(
       const Delegate::NextWorkInfo& next_work_info) override;
+  bool HandleNestedNativeLoopWithApplicationTasks(
+      bool application_tasks_desired) override;
 
   // An observer interface to give the scheduler an opportunity to log
   // information about MSGs before and after they are dispatched.
@@ -173,10 +179,32 @@
   // redundant timers.
   std::optional<TimeTicks> installed_native_timer_;
 
-  // This will become true when a native loop takes our kMsgHaveWork out of the
-  // system queue. It will be reset to false whenever DoRunLoop regains control.
-  // Used to decide whether ScheduleDelayedWork() should start a native timer.
-  bool in_native_loop_ = false;
+  // This is used to wake up the pump when the UIPumpImprovementsWin experiment
+  // is enabled.
+  WaitableEvent event_{WaitableEvent::ResetPolicy::AUTOMATIC};
+
+  enum class NestedState {
+    // There are no nested message loops running.
+    kNone,
+    // kMsgHaveWork was pumped from a native queue. The state will return to
+    // `kNormal` whenever DoRunLoop() regains control. In this state,
+    // ScheduleDelayedWork() will start a native timer.
+    //
+    // It is reset to `kNone` when DoRunLoop() gets control back after
+    // ProcessNextWindowsMessage() or DoWork().
+    kNestedNativeLoopDetected,
+    // HandleNestedNativeLoopWithApplicationTasks(true) was called (when a
+    // `ScopedAllowApplicationTasksInNativeNestedLoop` is instantiated). When
+    // running with `event_`, switches to pumping `kMsgHaveWork` MSGs when there
+    // are application tasks to be done during native runloops. Is a 'superset'
+    // of `kNestedNativeLoopDetected`, and will also start a native timer when
+    // ScheduleDelayedWork() is called.
+    //
+    // It is reset to `kNone` when:
+    //   - DoRunLoop() gets control back after ProcessNextWindowsMessage().
+    //   - HandleNestedNativeLoopWithApplicationTasks(false) is called.
+    kNestedNativeLoopAnnounced
+  } nested_state_ = NestedState::kNone;
 
   ObserverList<Observer>::Unchecked observers_;
 };
diff --git a/base/metrics/histogram.cc b/base/metrics/histogram.cc
index d7b6add7..2465add67 100644
--- a/base/metrics/histogram.cc
+++ b/base/metrics/histogram.cc
@@ -991,7 +991,7 @@
   if (count == 0)
     return;
   if (count < 0) {
-    NOTREACHED();
+    DUMP_WILL_BE_NOTREACHED_NORETURN();
     return;
   }
 
diff --git a/base/metrics/persistent_sample_map.cc b/base/metrics/persistent_sample_map.cc
index 4ff1d05c..e40e591 100644
--- a/base/metrics/persistent_sample_map.cc
+++ b/base/metrics/persistent_sample_map.cc
@@ -212,7 +212,8 @@
       SCOPED_CRASH_KEY_BOOL("PersistentSampleMap", "corrupted",
                             allocator->IsCorrupt());
 #endif  // !BUILDFLAG(IS_NACL)
-      NOTREACHED() << "corrupt=" << allocator->IsCorrupt();
+      DUMP_WILL_BE_NOTREACHED_NORETURN()
+          << "corrupt=" << allocator->IsCorrupt();
     }
     return 0;
   }
diff --git a/base/power_monitor/speed_limit_observer_win.cc b/base/power_monitor/speed_limit_observer_win.cc
index 700be2b..c714748c 100644
--- a/base/power_monitor/speed_limit_observer_win.cc
+++ b/base/power_monitor/speed_limit_observer_win.cc
@@ -32,7 +32,9 @@
 // speed-limit estimates.
 size_t kMovingAverageWindowSize = 10;
 
+#if BUILDFLAG(ENABLE_BASE_TRACING)
 constexpr const char kPowerTraceCategory[] = TRACE_DISABLED_BY_DEFAULT("power");
+#endif  // BUILDFLAG(ENABLE_BASE_TRACING)
 
 // From
 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa373184(v=vs.85).aspx.
@@ -70,6 +72,7 @@
   return true;
 }
 
+#if BUILDFLAG(ENABLE_BASE_TRACING)
 #if defined(ARCH_CPU_X86_FAMILY)
 // Returns the estimated CPU frequency by executing a tight loop of predictable
 // assembly instructions. The estimated frequency should be proportional and
@@ -106,6 +109,7 @@
   return estimated_frequency;
 }
 #endif
+#endif  // BUILDFLAG(ENABLE_BASE_TRACING)
 
 }  // namespace
 
@@ -137,6 +141,7 @@
   // Get the latest estimated throttling level (value between 0.0 and 1.0).
   float throttling_level = EstimateThrottlingLevel();
 
+#if BUILDFLAG(ENABLE_BASE_TRACING)
   // Emit trace events to investigate issues with power throttling. Run this
   // block only if tracing is running to avoid executing expensive calls to
   // EstimateCpuFrequency(...).
@@ -154,6 +159,7 @@
                   static_cast<unsigned int>(cpu_frequency / 1'000'000));
 #endif
   }
+#endif  // BUILDFLAG(ENABLE_BASE_TRACING)
 
   // Ignore the value if the global idleness is above 90% or throttling value
   // is very small. This approach avoids false alarms and removes noise from the
@@ -198,8 +204,10 @@
     callback_.Run(speed_limit_);
   }
 
+#if BUILDFLAG(ENABLE_BASE_TRACING)
   TRACE_COUNTER(kPowerTraceCategory, "speed_limit",
                 static_cast<unsigned int>(speed_limit));
+#endif  // BUILDFLAG(ENABLE_BASE_TRACING)
 }
 
 float SpeedLimitObserverWin::EstimateThrottlingLevel() {
@@ -228,7 +236,7 @@
   // 1, the `CurrentIdleState` will always be 0 and the C-States are not
   // supported.
   int num_non_idle_cpus = 0;
-  int num_active_cpus = 0;
+  [[maybe_unused]] int num_active_cpus = 0;
   float load_fraction_total = 0.0;
   for (size_t i = 0; i < num_cpus(); ++i) {
     // Amount of "non-idleness" is the distance from the max idle state.
@@ -259,7 +267,9 @@
       << " num_non_idle_cpus:" << num_non_idle_cpus;
   throttling_level = (load_fraction_total / num_cpus());
 
+#if BUILDFLAG(ENABLE_BASE_TRACING)
   TRACE_COUNTER(kPowerTraceCategory, "num_active_cpus", num_active_cpus);
+#endif  // BUILDFLAG(ENABLE_BASE_TRACING)
 
   return throttling_level;
 }
diff --git a/base/profiler/suspendable_thread_delegate_win.cc b/base/profiler/suspendable_thread_delegate_win.cc
index f691819..db470d9c 100644
--- a/base/profiler/suspendable_thread_delegate_win.cc
+++ b/base/profiler/suspendable_thread_delegate_win.cc
@@ -173,7 +173,7 @@
   ScopedDisablePriorityBoost disable_priority_boost(thread_handle_);
   bool resume_thread_succeeded =
       ::ResumeThread(thread_handle_) != static_cast<DWORD>(-1);
-  CHECK(resume_thread_succeeded) << "ResumeThread failed: " << GetLastError();
+  PCHECK(resume_thread_succeeded) << "ResumeThread failed";
 }
 
 bool SuspendableThreadDelegateWin::ScopedSuspendThread::WasSuccessful() const {
diff --git a/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
index 2a0d1988..579cfc9 100644
--- a/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
+++ b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
@@ -703,9 +703,7 @@
     // pump (this will be done anyway when the task exits).
     work_deduplicator_.OnWorkStarted();
   }
-  if (!pump_->HandleNestedNativeLoopWithApplicationTasks(
-          allowed ? MessagePump::NativeLoopStatus::kOnEntry
-                  : MessagePump::NativeLoopStatus::kOnExit)) {
+  if (!pump_->HandleNestedNativeLoopWithApplicationTasks(allowed)) {
     // Pump does not have its own support for native nested loops,
     // ThreadController must handle scheduling for upcoming tasks.
     if (allowed) {
diff --git a/base/task/task_features.cc b/base/task/task_features.cc
index 633f5566..2ec6a7a 100644
--- a/base/task/task_features.cc
+++ b/base/task/task_features.cc
@@ -53,6 +53,10 @@
              "ExplicitHighResolutionTimerWin",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kUIPumpImprovementsWin,
+             "UIPumpImprovementsWin",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kRunTasksByBatches,
              "RunTasksByBatches",
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)
diff --git a/base/task/task_features.h b/base/task/task_features.h
index 3020194..8e943e1 100644
--- a/base/task/task_features.h
+++ b/base/task/task_features.h
@@ -57,6 +57,11 @@
 // based on explicit DelayPolicy rather than based on a threshold.
 BASE_EXPORT BASE_DECLARE_FEATURE(kExplicitHighResolutionTimerWin);
 
+// Under this feature, the Windows UI pump uses a WaitableEvent to wake itself
+// up when not in a native nested loop. It also uses different control flow,
+// calling Win32 MessagePump functions less often.
+BASE_EXPORT BASE_DECLARE_FEATURE(kUIPumpImprovementsWin);
+
 // Feature to run tasks by batches before pumping out messages.
 BASE_EXPORT BASE_DECLARE_FEATURE(kRunTasksByBatches);
 
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/ConditionWaiter.java b/base/test/android/javatests/src/org/chromium/base/test/transit/ConditionWaiter.java
index b88e9f2..ae2f642 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/ConditionWaiter.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/ConditionWaiter.java
@@ -61,6 +61,10 @@
         }
 
         private void startTimer() {
+            if (mTimeStarted > 0) {
+                return;
+            }
+
             mTimeStarted = getNow();
             mTimeUnfulfilled = mTimeStarted;
         }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckIn.java b/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckIn.java
index 6466c24..4d7d21f 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckIn.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckIn.java
@@ -33,10 +33,36 @@
     }
 
     void enterSync() {
+        // TODO(crbug.com/333735412): Unify Trip#travelSyncInternal(), FacilityCheckIn#enterSync()
+        // and FacilityCheckOut#exitSync().
         onBeforeTransition();
-        triggerTransition();
         List<ConditionWait> waits = createWaits();
-        waitUntilEntry(waits);
+
+        if (mOptions.mTries == 1) {
+            triggerTransition();
+            Log.i(TAG, "Triggered transition, waiting to enter %s", mFacility);
+            waitUntilEntry(waits);
+        } else {
+            for (int tryNumber = 1; tryNumber <= mOptions.mTries; tryNumber++) {
+                try {
+                    triggerTransition();
+                    Log.i(
+                            TAG,
+                            "Triggered transition (try #%d/%d), waiting to enter %s",
+                            tryNumber,
+                            mOptions.mTries,
+                            mFacility);
+                    waitUntilEntry(waits);
+                    break;
+                } catch (TravelException e) {
+                    Log.w(TAG, "Try #%d failed", tryNumber, e);
+                    if (tryNumber >= mOptions.mTries) {
+                        throw e;
+                    }
+                }
+            }
+        }
+
         onAfterTransition();
         PublicTransitConfig.maybePauseAfterTransition(mFacility);
     }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckOut.java b/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckOut.java
index 692538c..3d915a84 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckOut.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckOut.java
@@ -34,10 +34,36 @@
     }
 
     void exitSync() {
+        // TODO(crbug.com/333735412): Unify Trip#travelSyncInternal(), FacilityCheckIn#enterSync()
+        // and FacilityCheckOut#exitSync().
         onBeforeTransition();
-        triggerTransition();
         List<ConditionWait> waits = createWaits();
-        waitUntilExit(waits);
+
+        if (mOptions.mTries == 1) {
+            triggerTransition();
+            Log.i(TAG, "Triggered transition, waiting to exit %s", mFacility);
+            waitUntilExit(waits);
+        } else {
+            for (int tryNumber = 1; tryNumber <= mOptions.mTries; tryNumber++) {
+                try {
+                    triggerTransition();
+                    Log.i(
+                            TAG,
+                            "Triggered transition (try #%d/%d), waiting to exit %s",
+                            tryNumber,
+                            mOptions.mTries,
+                            mFacility);
+                    waitUntilExit(waits);
+                    break;
+                } catch (TravelException e) {
+                    Log.w(TAG, "Try #%d failed", tryNumber, e);
+                    if (tryNumber >= mOptions.mTries) {
+                        throw e;
+                    }
+                }
+            }
+        }
+
         onAfterTransition();
         PublicTransitConfig.maybePauseAfterTransition(mFacility);
     }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java b/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java
index 773f7fee..e79ca0e 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java
@@ -31,7 +31,12 @@
 
     protected void triggerTransition() {
         if (mTrigger != null) {
-            mTrigger.triggerTransition();
+            try {
+                mTrigger.triggerTransition();
+            } catch (Exception e) {
+                throw TravelException.newTravelException(
+                        "Exception thrown by Transition trigger", e);
+            }
         }
     }
 
@@ -56,12 +61,18 @@
         return newOptions().withTimeout(timeoutMs).build();
     }
 
+    /** Convenience method equivalent to newOptions().withRetry().build(). */
+    public static TransitionOptions retryOption() {
+        return newOptions().withRetry().build();
+    }
+
     /** Options to configure the Transition. */
     public static class TransitionOptions {
 
         static final TransitionOptions DEFAULT = new TransitionOptions();
         @Nullable List<Condition> mTransitionConditions;
         long mTimeoutMs;
+        int mTries = 1;
 
         private TransitionOptions() {}
 
@@ -90,6 +101,15 @@
                 mTimeoutMs = timeoutMs;
                 return this;
             }
+
+            /**
+             * Retry the transition trigger once, if the transition does not finish within the
+             * timeout.
+             */
+            public Builder withRetry() {
+                mTries = 2;
+                return this;
+            }
         }
     }
 }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/Trip.java b/base/test/android/javatests/src/org/chromium/base/test/transit/Trip.java
index f1442cc..846127cd 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/Trip.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/Trip.java
@@ -73,6 +73,8 @@
     }
 
     private void travelSyncInternal() {
+        // TODO(crbug.com/333735412): Unify Trip#travelSyncInternal(), FacilityCheckIn#enterSync()
+        // and FacilityCheckOut#exitSync().
         embark();
         if (mOrigin != null) {
             Log.i(TAG, "Trip %d: Embarked at %s towards %s", mId, mOrigin, mDestination);
@@ -80,10 +82,32 @@
             Log.i(TAG, "Trip %d: Starting at entry point %s", mId, mDestination);
         }
 
-        triggerTransition();
-        Log.i(TAG, "Trip %d: Triggered transition, waiting to arrive at %s", mId, mDestination);
+        if (mOptions.mTries == 1) {
+            triggerTransition();
+            Log.i(TAG, "Trip %d: Triggered transition, waiting to arrive at %s", mId, mDestination);
+            waitUntilArrival();
+        } else {
+            for (int tryNumber = 1; tryNumber <= mOptions.mTries; tryNumber++) {
+                try {
+                    triggerTransition();
+                    Log.i(
+                            TAG,
+                            "Trip %d: Triggered transition (try #%d/%d), waiting to arrive at %s",
+                            mId,
+                            tryNumber,
+                            mOptions.mTries,
+                            mDestination);
+                    waitUntilArrival();
+                    break;
+                } catch (TravelException e) {
+                    Log.w(TAG, "Try #%d failed", tryNumber, e);
+                    if (tryNumber >= mOptions.mTries) {
+                        throw e;
+                    }
+                }
+            }
+        }
 
-        waitUntilArrival();
         Log.i(TAG, "Trip %d: Arrived at %s", mId, mDestination);
 
         PublicTransitConfig.maybePauseAfterTransition(mDestination);
diff --git a/build/android/pylib/local/device/local_device_gtest_run.py b/build/android/pylib/local/device/local_device_gtest_run.py
index 8d0dedf..add9066e 100644
--- a/build/android/pylib/local/device/local_device_gtest_run.py
+++ b/build/android/pylib/local/device/local_device_gtest_run.py
@@ -764,6 +764,10 @@
         logging.critical('Logcat saved to %s', logcat_file.Link())
 
   #override
+  def _GetUniqueTestName(self, test):
+    return gtest_test_instance.TestNameWithoutDisabledPrefix(test)
+
+  #override
   def _RunTest(self, device, test):
     # Run the test.
     timeout = (self._test_instance.shard_timeout *
diff --git a/build/android/pylib/utils/device_dependencies.py b/build/android/pylib/utils/device_dependencies.py
index 5c60875..97f08c12 100644
--- a/build/android/pylib/utils/device_dependencies.py
+++ b/build/android/pylib/utils/device_dependencies.py
@@ -29,9 +29,6 @@
     # Chrome external extensions config file.
     re.compile(r'.*external_extensions\.json'),
 
-    # Exists just to test the compile, not to be run.
-    re.compile(r'.*jni_generator_tests'),
-
     # v8's blobs and icu data get packaged into APKs.
     re.compile(r'.*snapshot_blob.*\.bin'),
     re.compile(r'.*icudtl\.bin'),
diff --git a/chrome/VERSION b/chrome/VERSION
index bd3381f..c298d9ca 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=125
 MINOR=0
-BUILD=6412
+BUILD=6413
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 675802ba..0b0a285 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -1232,6 +1232,7 @@
       "//components/embedder_support/android:context_menu_java",
       "//components/embedder_support/android:junit_test_support",
       "//components/embedder_support/android:util_java",
+      "//components/external_intents/android:java",
       "//components/externalauth/android:java",
       "//components/externalauth/android:junit",
       "//components/favicon/android:java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 755c37c..35b626b9 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -96,6 +96,7 @@
   "java/src/org/chromium/chrome/browser/app/tab_activity_glue/ReparentingDelegateFactory.java",
   "java/src/org/chromium/chrome/browser/app/tab_activity_glue/ReparentingTask.java",
   "java/src/org/chromium/chrome/browser/app/tab_activity_glue/TabReparentingController.java",
+  "java/src/org/chromium/chrome/browser/app/tabmodel/ArchivedTabModelOrchestrator.java",
   "java/src/org/chromium/chrome/browser/app/tabmodel/AsyncTabParamsManagerSingleton.java",
   "java/src/org/chromium/chrome/browser/app/tabmodel/ChromeNextTabPolicySupplier.java",
   "java/src/org/chromium/chrome/browser/app/tabmodel/ChromeTabModelFilterFactory.java",
@@ -1005,6 +1006,7 @@
   "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckUpdatesDelegateImpl.java",
   "java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java",
   "java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java",
+  "java/src/org/chromium/chrome/browser/searchwidget/SearchActivityTabDelegateFactory.java",
   "java/src/org/chromium/chrome/browser/searchwidget/SearchActivityUtils.java",
   "java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java",
   "java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java",
@@ -1103,6 +1105,8 @@
   "java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateTabHelper.java",
   "java/src/org/chromium/chrome/browser/tab/RedirectHandlerTabHelper.java",
   "java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java",
+  "java/src/org/chromium/chrome/browser/tab/TabArchiveSettings.java",
+  "java/src/org/chromium/chrome/browser/tab/TabArchiver.java",
   "java/src/org/chromium/chrome/browser/tab/TabBrowserControlsConstraintsHelper.java",
   "java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java",
   "java/src/org/chromium/chrome/browser/tab/TabBuilder.java",
@@ -1132,6 +1136,8 @@
   "java/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorController.java",
   "java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java",
   "java/src/org/chromium/chrome/browser/tabbed_mode/TabbedSystemUiCoordinator.java",
+  "java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreator.java",
+  "java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabModelSelectorImpl.java",
   "java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java",
   "java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModelImplCreator.java",
   "java/src/org/chromium/chrome/browser/tabmodel/PendingTabClosureManager.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 1a8fc85..e0c534a 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -311,6 +311,7 @@
   "junit/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceMetricsTest.java",
   "junit/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotificationTest.java",
   "junit/src/org/chromium/chrome/browser/search_engines/settings/SearchEngineAdapterTest.java",
+  "junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityTabDelegateFactoryUnitTest.java",
   "junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityUnitTest.java",
   "junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityUtilsUnitTest.java",
   "junit/src/org/chromium/chrome/browser/settings/SettingsActivityUnitTest.java",
@@ -332,6 +333,7 @@
   "junit/src/org/chromium/chrome/browser/sync/SyncErrorNotifierTest.java",
   "junit/src/org/chromium/chrome/browser/sync/ui/SyncErrorMessageImpressionTrackerTest.java",
   "junit/src/org/chromium/chrome/browser/tab/RequestDesktopUtilsUnitTest.java",
+  "junit/src/org/chromium/chrome/browser/tab/TabArchiveSettingsTest.java",
   "junit/src/org/chromium/chrome/browser/tab/TabAttributesTest.java",
   "junit/src/org/chromium/chrome/browser/tab/TabBrowserControlsConstraintsHelperTest.java",
   "junit/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelperTest.java",
@@ -345,6 +347,7 @@
   "junit/src/org/chromium/chrome/browser/tab/tab_restore/HistoricalTabSaverImplUnitTest.java",
   "junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java",
   "junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorControllerUnitTest.java",
+  "junit/src/org/chromium/chrome/browser/tabmodel/ArchivedTabModelSelectorImplTest.java",
   "junit/src/org/chromium/chrome/browser/tabmodel/PendingTabClosureManagerTest.java",
   "junit/src/org/chromium/chrome/browser/tabmodel/TabModelImplUnitTest.java",
   "junit/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorImplTest.java",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index d4ed2f2..32592b9 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -40,6 +40,7 @@
   "javatests/src/org/chromium/chrome/browser/ServicificationBackgroundServiceTest.java",
   "javatests/src/org/chromium/chrome/browser/ShareIntentTest.java",
   "javatests/src/org/chromium/chrome/browser/SmartClipProviderTest.java",
+  "javatests/src/org/chromium/chrome/browser/TabArchiverTest.java",
   "javatests/src/org/chromium/chrome/browser/TabCountLabelTest.java",
   "javatests/src/org/chromium/chrome/browser/TabObserverTest.java",
   "javatests/src/org/chromium/chrome/browser/TabTest.java",
@@ -504,6 +505,7 @@
   "javatests/src/org/chromium/chrome/browser/tab/state/TabStateFlatBufferTest.java",
   "javatests/src/org/chromium/chrome/browser/tab/tab_restore/HistoricalTabSaverImplTest.java",
   "javatests/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorControllerTest.java",
+  "javatests/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreatorTest.java",
   "javatests/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreatorTest.java",
   "javatests/src/org/chromium/chrome/browser/tabmodel/ContextMenuLoadUrlParamsTest.java",
   "javatests/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModelTest.java",
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 2560e49..36f6088c 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
@@ -34,6 +34,7 @@
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.ColorPickerCoordinator.ColorPickerLayoutType;
 import org.chromium.chrome.browser.tasks.tab_management.TabListEditorCoordinator.TabListEditorController;
+import org.chromium.chrome.browser.tasks.tab_management.TabUiMetricsHelper.TabGroupColorChangeActionType;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
@@ -272,6 +273,8 @@
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)) {
             return (view) -> {
                 showColorPickerPopup(view);
+                TabUiMetricsHelper.recordTabGroupColorChangeActionMetrics(
+                        TabGroupColorChangeActionType.VIA_COLOR_ICON);
             };
         }
         return null;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
index f81be1e1..221a50bf 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
@@ -42,6 +42,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.TabListEditorCoordinator.TabListEditorController;
+import org.chromium.chrome.browser.tasks.tab_management.TabUiMetricsHelper.TabGroupColorChangeActionType;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiMetricsHelper.TabListEditorOpenMetricGroups;
 import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
@@ -360,6 +361,8 @@
 
                     if (result == R.id.edit_group_color) {
                         mShowColorPickerPopupRunnable.run();
+                        TabUiMetricsHelper.recordTabGroupColorChangeActionMetrics(
+                                TabGroupColorChangeActionType.VIA_OVERFLOW_MENU);
                     }
                 };
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationDialogManager.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationDialogManager.java
index aa0b0d4..4142b003 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationDialogManager.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationDialogManager.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.tasks.tab_management;
 
 import android.app.Activity;
+import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
@@ -21,6 +22,8 @@
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver;
 import org.chromium.chrome.browser.tasks.tab_management.ColorPickerCoordinator.ColorPickerLayoutType;
+import org.chromium.chrome.browser.tasks.tab_management.TabUiMetricsHelper.TabGroupCreationDialogResultAction;
+import org.chromium.chrome.browser.tasks.tab_management.TabUiMetricsHelper.TabGroupCreationFinalSelections;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.tab_groups.TabGroupColorId;
 import org.chromium.ui.modaldialog.DialogDismissalCause;
@@ -31,6 +34,7 @@
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.List;
+import java.util.Objects;
 
 /** Manager of the observers that trigger a modal dialog on new tab group creation. */
 public class TabGroupCreationDialogManager implements Destroyable {
@@ -72,8 +76,8 @@
                             /* isIncognito= */ false,
                             ColorPickerLayoutType.DYNAMIC,
                             null);
-            final @TabGroupColorId int colorId = filter.getTabGroupColor(rootId);
-            colorPickerCoordinator.setSelectedColorItem(colorId);
+            final @TabGroupColorId int defaultColorId = filter.getTabGroupColor(rootId);
+            colorPickerCoordinator.setSelectedColorItem(defaultColorId);
 
             LinearLayout linearLayout =
                     (LinearLayout) customView.findViewById(R.id.creation_dialog_layout);
@@ -103,19 +107,38 @@
                                     || dismissalCause
                                             == DialogDismissalCause
                                                     .NAVIGATE_BACK_OR_TOUCH_OUTSIDE) {
-                                final @TabGroupColorId int color =
+                                final @TabGroupColorId int currentColorId =
                                         colorPickerCoordinator.getSelectedColorSupplier().get();
-                                filter.setTabGroupColor(rootId, color);
+                                boolean didChangeColor = currentColorId != defaultColorId;
+                                filter.setTabGroupColor(rootId, currentColorId);
 
                                 // Only save the group title input text if it has been changed from
-                                // the suggested default title.
+                                // the suggested default title and if it is not empty.
                                 String inputGroupTitle = groupTitle.getTrimmedText();
-                                if (!defaultGroupTitle.equals(inputGroupTitle)) {
+                                boolean didChangeTitle =
+                                        !Objects.equals(defaultGroupTitle, inputGroupTitle);
+                                if (didChangeTitle && !TextUtils.isEmpty(inputGroupTitle)) {
                                     filter.setTabGroupTitle(rootId, groupTitle.getTrimmedText());
                                 }
 
                                 // Refresh the GTS tab list with the newly set color and title.
                                 mOnDialogAcceptedRunnable.run();
+                                recordDialogSelectionHistogram(didChangeColor, didChangeTitle);
+
+                                if (dismissalCause
+                                        == DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE) {
+                                    TabUiMetricsHelper
+                                            .recordTabGroupCreationDialogResultActionMetrics(
+                                                    TabGroupCreationDialogResultAction
+                                                            .DISMISSED_SCRIM_OR_BACKPRESS);
+                                } else {
+                                    TabUiMetricsHelper
+                                            .recordTabGroupCreationDialogResultActionMetrics(
+                                                    TabGroupCreationDialogResultAction.ACCEPTED);
+                                }
+                            } else {
+                                TabUiMetricsHelper.recordTabGroupCreationDialogResultActionMetrics(
+                                        TabGroupCreationDialogResultAction.DISMISSED_OTHER);
                             }
                         }
                     };
@@ -221,6 +244,24 @@
         return new ShowDialogDelegate();
     }
 
+    private void recordDialogSelectionHistogram(boolean didChangeColor, boolean didChangeTitle) {
+        if (didChangeColor && didChangeTitle) {
+            TabUiMetricsHelper.recordTabGroupCreationFinalSelectionsHistogram(
+                    TabGroupCreationFinalSelections.CHANGED_COLOR_AND_TITLE);
+        } else {
+            if (didChangeColor) {
+                TabUiMetricsHelper.recordTabGroupCreationFinalSelectionsHistogram(
+                        TabGroupCreationFinalSelections.CHANGED_COLOR);
+            } else if (didChangeTitle) {
+                TabUiMetricsHelper.recordTabGroupCreationFinalSelectionsHistogram(
+                        TabGroupCreationFinalSelections.CHANGED_TITLE);
+            } else {
+                TabUiMetricsHelper.recordTabGroupCreationFinalSelectionsHistogram(
+                        TabGroupCreationFinalSelections.DEFAULT_COLOR_AND_TITLE);
+            }
+        }
+    }
+
     void setShowDialogDelegateForTesting(ShowDialogDelegate delegate) {
         mShowDialogDelegate = delegate;
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiMetricsHelper.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiMetricsHelper.java
index b201f08..89b9a98 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiMetricsHelper.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiMetricsHelper.java
@@ -75,6 +75,57 @@
         int OPEN_FROM_DIALOG = 1;
     }
 
+    // These values are persisted to logs. Entries should not be renumbered and
+    // numeric values should never be reused.
+    @IntDef({
+        TabGroupCreationFinalSelections.DEFAULT_COLOR_AND_TITLE,
+        TabGroupCreationFinalSelections.CHANGED_COLOR,
+        TabGroupCreationFinalSelections.CHANGED_TITLE,
+        TabGroupCreationFinalSelections.CHANGED_COLOR_AND_TITLE,
+        TabGroupCreationFinalSelections.NUM_ENTRIES
+    })
+    public @interface TabGroupCreationFinalSelections {
+        int DEFAULT_COLOR_AND_TITLE = 0;
+        int CHANGED_COLOR = 1;
+        int CHANGED_TITLE = 2;
+        int CHANGED_COLOR_AND_TITLE = 3;
+
+        // Be sure to also update enums.xml when updating these values.
+        int NUM_ENTRIES = 4;
+    }
+
+    // These values are persisted to logs. Entries should not be renumbered and
+    // numeric values should never be reused.
+    @IntDef({
+        TabGroupCreationDialogResultAction.ACCEPTED,
+        TabGroupCreationDialogResultAction.DISMISSED_SCRIM_OR_BACKPRESS,
+        TabGroupCreationDialogResultAction.DISMISSED_OTHER,
+        TabGroupCreationDialogResultAction.NUM_ENTRIES
+    })
+    public @interface TabGroupCreationDialogResultAction {
+        int ACCEPTED = 0;
+        int DISMISSED_SCRIM_OR_BACKPRESS = 1;
+        int DISMISSED_OTHER = 2;
+
+        // Be sure to also update enums.xml when updating these values.
+        int NUM_ENTRIES = 3;
+    }
+
+    // These values are persisted to logs. Entries should not be renumbered and
+    // numeric values should never be reused.
+    @IntDef({
+        TabGroupColorChangeActionType.VIA_COLOR_ICON,
+        TabGroupColorChangeActionType.VIA_OVERFLOW_MENU,
+        TabGroupColorChangeActionType.NUM_ENTRIES
+    })
+    public @interface TabGroupColorChangeActionType {
+        int VIA_COLOR_ICON = 0;
+        int VIA_OVERFLOW_MENU = 1;
+
+        // Be sure to also update enums.xml when updating these values.
+        int NUM_ENTRIES = 2;
+    }
+
     // Histograms
     public static void recordEditorTimeSinceLastShownHistogram() {
         long timestampMillis = System.currentTimeMillis();
@@ -180,4 +231,28 @@
                                 + " when calling recordSelectionEditorOpenMetrics with V2 enabled.";
         }
     }
+
+    public static void recordTabGroupCreationFinalSelectionsHistogram(
+            @TabGroupCreationFinalSelections int action) {
+        RecordHistogram.recordEnumeratedHistogram(
+                "Android.TabGroupParity.TabGroupCreationFinalSelections",
+                action,
+                TabGroupCreationFinalSelections.NUM_ENTRIES);
+    }
+
+    public static void recordTabGroupCreationDialogResultActionMetrics(
+            @TabGroupCreationDialogResultAction int action) {
+        RecordHistogram.recordEnumeratedHistogram(
+                "Android.TabGroupParity.TabGroupCreationDialogResultAction",
+                action,
+                TabGroupCreationDialogResultAction.NUM_ENTRIES);
+    }
+
+    public static void recordTabGroupColorChangeActionMetrics(
+            @TabGroupColorChangeActionType int action) {
+        RecordHistogram.recordEnumeratedHistogram(
+                "Android.TabGroupParity.TabGroupColorChangeActionType",
+                action,
+                TabGroupColorChangeActionType.NUM_ENTRIES);
+    }
 }
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
index 11226e0..b9ebefd 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
@@ -111,6 +111,7 @@
 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.HistogramWatcher;
 import org.chromium.base.test.util.RequiresRestart;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -488,6 +489,13 @@
         verifyGroupCreationDialogOpenedAndDismiss();
         verifyTabSwitcherCardCount(cta, 1);
 
+        // Expect the color icon to be clicked.
+        var histograms =
+                HistogramWatcher.newBuilder()
+                        .expectIntRecordTimes(
+                                "Android.TabGroupParity.TabGroupColorChangeActionType", 0, 3)
+                        .build();
+
         // Open dialog and click the color icon to show the color picker.
         openDialogFromTabSwitcherAndVerify(cta, 2, null);
         onView(withId(R.id.tab_group_color_icon)).perform(click());
@@ -517,6 +525,7 @@
         // Clicking ScrimView should close the color picker pop up.
         onView(withId(R.id.tab_group_color_icon)).perform(click());
         clickScrimToExitDialog(cta);
+        histograms.assertExpected();
     }
 
     @Test
@@ -543,6 +552,11 @@
         verifyGroupCreationDialogOpenedAndDismiss();
         verifyTabSwitcherCardCount(cta, 1);
 
+        // Expect the edit color menu item to be clicked.
+        HistogramWatcher watcher =
+                HistogramWatcher.newSingleRecordWatcher(
+                        "Android.TabGroupParity.TabGroupColorChangeActionType", 1);
+
         // Open dialog and click the toolbar menu item to show the color picker.
         openDialogFromTabSwitcherAndVerify(cta, 2, null);
         openDialogToolbarMenuAndVerify(cta);
@@ -561,6 +575,7 @@
                                 withId(R.id.color_picker_container)))
                 .check(doesNotExist());
         clickScrimToExitDialog(cta);
+        watcher.assertExpected();
     }
 
     @Test
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutTest.java
index 1e3660c..7ea44f7 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutTest.java
@@ -1697,10 +1697,17 @@
         verifyTabSwitcherCardCount(cta, 2);
         // Create a tab group.
         mergeAllNormalTabsToAGroup(cta);
+
+        // Expect that the the dialog is dismissed via another action.
+        HistogramWatcher watcher =
+                HistogramWatcher.newSingleRecordWatcher(
+                        "Android.TabGroupParity.TabGroupCreationDialogResultAction", 2);
+
         verifyGroupCreationDialogOpenedAndDismiss(cta);
         // Verify the color icon exists.
         onView(allOf(withId(R.id.tab_favicon), withParent(withId(R.id.card_view))))
                 .check(matches(isDisplayed()));
+        watcher.assertExpected();
     }
 
     @Test
@@ -1731,6 +1738,15 @@
                         blueColor);
         onView(withContentDescription(notSelectedStringBlue)).perform(click());
 
+        // Expect a changed color and title selection to be recorded and an acceptance action.
+        var histograms =
+                HistogramWatcher.newBuilder()
+                        .expectIntRecord(
+                                "Android.TabGroupParity.TabGroupCreationFinalSelections", 3)
+                        .expectIntRecord(
+                                "Android.TabGroupParity.TabGroupCreationDialogResultAction", 0)
+                        .build();
+
         // Accept the change.
         onView(withId(R.id.positive_button)).perform(click());
         verifyModalDialogHidingAnimationCompleteInTabSwitcher();
@@ -1738,6 +1754,7 @@
         verifyFirstCardTitle("Test");
         // Verify the color icon exists.
         verifyFirstCardColor(TabGroupColorId.BLUE);
+        histograms.assertExpected();
     }
 
     @Test
@@ -1759,6 +1776,11 @@
         // Close the soft keyboard that appears when the dialog is shown.
         closeSoftKeyboard();
 
+        // Expect changed color and title selection to be recorded.
+        HistogramWatcher watcher =
+                HistogramWatcher.newSingleRecordWatcher(
+                        "Android.TabGroupParity.TabGroupCreationFinalSelections", 0);
+
         // Accept without changing the title.
         onView(withId(R.id.positive_button)).perform(click());
         verifyModalDialogHidingAnimationCompleteInTabSwitcher();
@@ -1766,6 +1788,45 @@
         // Check that the title change is reflected.
         verifyFirstCardTitle("2 tabs");
         verifyFirstCardColor(TabGroupColorId.GREY);
+        watcher.assertExpected();
+    }
+
+    @Test
+    @MediumTest
+    @EnableFeatures({ChromeFeatureList.TAB_GROUP_PARITY_ANDROID})
+    public void testTabGroupCreation_dismissEmptyTitle() {
+        final ChromeTabbedActivity cta = mActivityTestRule.getActivity();
+        createTabs(cta, false, 2);
+        enterTabSwitcher(cta);
+        verifyTabSwitcherCardCount(cta, 2);
+
+        // Create a tab group.
+        mergeAllNormalTabsToAGroup(cta);
+        // Verify the creation dialog exists.
+        verifyModalDialogShowingAnimationCompleteInTabSwitcher();
+        onViewWaiting(withId(R.id.creation_dialog_layout), /* checkRootDialog= */ true)
+                .check(matches(isDisplayed()));
+
+        // Close the soft keyboard that appears when the dialog is shown.
+        closeSoftKeyboard();
+
+        // Change the title.
+        editGroupCreationDialogTitle(cta, "");
+        // Change the color.
+        String blueColor =
+                cta.getString(R.string.accessibility_tab_group_color_picker_color_item_blue);
+        String notSelectedStringBlue =
+                cta.getString(
+                        R.string
+                                .accessibility_tab_group_color_picker_color_item_not_selected_description,
+                        blueColor);
+        onView(withContentDescription(notSelectedStringBlue)).perform(click());
+
+        // Enact a backpress to dismiss the dialog.
+        Espresso.pressBack();
+        verifyModalDialogHidingAnimationCompleteInTabSwitcher();
+        // Check that the title change has reverted to the default null state.
+        verifyFirstCardTitle("2 tabs");
     }
 
     @Test
@@ -1825,6 +1886,11 @@
                         blueColor);
         onView(withContentDescription(notSelectedStringBlue)).perform(click());
 
+        // Expect that the dismiss action is recorded.
+        HistogramWatcher watcher =
+                HistogramWatcher.newSingleRecordWatcher(
+                        "Android.TabGroupParity.TabGroupCreationDialogResultAction", 1);
+
         // Enact a backpress to dismiss the dialog.
         Espresso.pressBack();
         verifyModalDialogHidingAnimationCompleteInTabSwitcher();
@@ -1832,6 +1898,7 @@
         verifyFirstCardTitle("Test");
         // Verify the color icon exists.
         verifyFirstCardColor(TabGroupColorId.BLUE);
+        watcher.assertExpected();
     }
 
     @Test
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ArchivedTabModelOrchestrator.java b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ArchivedTabModelOrchestrator.java
new file mode 100644
index 0000000..dd913b2f
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ArchivedTabModelOrchestrator.java
@@ -0,0 +1,194 @@
+// 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.app.tabmodel;
+
+import android.content.Context;
+
+import org.chromium.base.ApplicationState;
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.ApplicationStatus.ApplicationStateListener;
+import org.chromium.base.Callback;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.lifetime.Destroyable;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.profiles.ProfileKeyedMap;
+import org.chromium.chrome.browser.tab.TabArchiver;
+import org.chromium.chrome.browser.tab_ui.TabContentManager;
+import org.chromium.chrome.browser.tabmodel.ArchivedTabCreator;
+import org.chromium.chrome.browser.tabmodel.ArchivedTabModelSelectorImpl;
+import org.chromium.chrome.browser.tabmodel.AsyncTabParamsManager;
+import org.chromium.chrome.browser.tabmodel.NextTabPolicy;
+import org.chromium.chrome.browser.tabmodel.TabCreator;
+import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tabmodel.TabPersistentStore;
+import org.chromium.chrome.browser.tabmodel.TabbedModeTabPersistencePolicy;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * Glue-level class that manages the lifetime of {@link TabPersistentStore} and {@link
+ * TabModelSelectorImpl} for archived tabs. Uses the base logic from TabModelOrchestrator to wire
+ * the store and selector. This class is tied to a profile, and will be cleaned up when the profile
+ * goes away.
+ */
+public class ArchivedTabModelOrchestrator extends TabModelOrchestrator implements Destroyable {
+    public static final String ARCHIVED_TAB_SELECTOR_UNIQUE_TAG = "archived";
+
+    private static ProfileKeyedMap<ArchivedTabModelOrchestrator> sProfileMap;
+
+    // TODO(crbug.com/333572160): Rely on PKM destroy infra when it's working.
+    private final ApplicationStatus.ApplicationStateListener mApplicationStateListener =
+            new ApplicationStateListener() {
+                @Override
+                public void onApplicationStateChange(@ApplicationState int newState) {
+                    if (ApplicationStatus.isEveryActivityDestroyed()) {
+                        // Destroy the profile map, which will also destroy all orchestrators.
+                        // Null it out so if we go from 1 -> 0 -> 1 activities, #getForProfile
+                        // will still work.
+                        sProfileMap.destroy();
+                        sProfileMap = null;
+
+                        ApplicationStatus.unregisterApplicationStateListener(this);
+                    }
+                }
+            };
+
+    private final Profile mProfile;
+    // TODO(crbug.com/331689555): Figure out how to do synchronization. Only one instance should
+    // really be using this at a time and it makes things like undo messy if it is supported in
+    // multiple places simultaneously.
+    private final TabCreatorManager mArchivedTabCreatorManager;
+
+    private WindowAndroid mWindow;
+    private TabArchiver mTabArchiver;
+    private TabCreator mArchivedTabCreator;
+    private boolean mNativeLibraryReadyCalled;
+    private boolean mLoadStateCalled;
+    private boolean mRestoreTabsCalled;
+    private boolean mDestroyCalled;
+
+    /**
+     * Returns the ArchivedTabModelOrchestrator that corresponds to the given profile. Must be
+     * called after native initialization
+     *
+     * @param profile The {@link Profile} to build the ArchivedTabModelOrchestrator with.
+     * @return The corresponding {@link ArchivedTabModelOrchestrator}.
+     */
+    public static ArchivedTabModelOrchestrator getForProfile(Profile profile) {
+        if (sProfileMap == null) {
+            ThreadUtils.assertOnUiThread();
+            sProfileMap =
+                    ProfileKeyedMap.createMapOfDestroyables(
+                            ProfileKeyedMap.ProfileSelection.REDIRECTED_TO_ORIGINAL);
+        }
+        return sProfileMap.getForProfile(profile, ArchivedTabModelOrchestrator::new);
+    }
+
+    private ArchivedTabModelOrchestrator(Profile profile) {
+        mProfile = profile;
+        mArchivedTabCreatorManager =
+                new TabCreatorManager() {
+                    @Override
+                    public TabCreator getTabCreator(boolean incognito) {
+                        assert !incognito : "Archived tab model does not support incognito.";
+                        return mArchivedTabCreator;
+                    }
+                };
+        ApplicationStatus.registerApplicationStateListener(mApplicationStateListener);
+    }
+
+    @Override
+    public void destroy() {
+        if (mDestroyCalled) return;
+
+        mWindow.destroy();
+        mWindow = null;
+
+        super.destroy();
+        mDestroyCalled = true;
+    }
+
+    /**
+     * Creates the {@link TabModelSelector} and the {@link TabPersistentStore} if not already
+     * created.
+     */
+    public void maybeCreateTabModels() {
+        if (mArchivedTabCreator != null) return;
+
+        Context context = ContextUtils.getApplicationContext();
+        // TODO(crbug.com/331841977): Investigate removing the WindowAndroid requirement when
+        // creating tabs.
+        mWindow = new WindowAndroid(context);
+        mArchivedTabCreator = new ArchivedTabCreator(mWindow);
+
+        AsyncTabParamsManager asyncTabParamsManager = AsyncTabParamsManagerSingleton.getInstance();
+        mTabModelSelector =
+                new ArchivedTabModelSelectorImpl(
+                        mProfile,
+                        mArchivedTabCreatorManager,
+                        new ChromeTabModelFilterFactory(context),
+                        () -> NextTabPolicy.LOCATIONAL,
+                        asyncTabParamsManager);
+
+        mTabPersistencePolicy =
+                new TabbedModeTabPersistencePolicy(
+                        TabPersistentStore.getMetadataFileName(ARCHIVED_TAB_SELECTOR_UNIQUE_TAG),
+                        /* otherMetadataFileName= */ null,
+                        /* mergeTabsOnStartup= */ false,
+                        /* tabMergingEnabled= */ false);
+        mTabPersistentStore =
+                new TabPersistentStore(
+                        mTabPersistencePolicy, mTabModelSelector, mArchivedTabCreatorManager);
+        mTabArchiver =
+                new TabArchiver(
+                        mArchivedTabCreator,
+                        mTabModelSelector.getModel(/* incognito= */ false),
+                        asyncTabParamsManager);
+
+        wireSelectorAndStore();
+        markTabModelsInitialized();
+    }
+
+    @Override
+    public void onNativeLibraryReady(TabContentManager tabContentManager) {
+        if (mNativeLibraryReadyCalled) return;
+        mNativeLibraryReadyCalled = true;
+
+        super.onNativeLibraryReady(tabContentManager);
+    }
+
+    @Override
+    public void loadState(
+            boolean ignoreIncognitoFiles, Callback<String> onStandardActiveIndexRead) {
+        if (mLoadStateCalled) return;
+        mLoadStateCalled = true;
+        assert ignoreIncognitoFiles : "Must ignore incognito files for archived tabs.";
+        super.loadState(ignoreIncognitoFiles, onStandardActiveIndexRead);
+    }
+
+    @Override
+    public void restoreTabs(boolean setActiveTab) {
+        if (mRestoreTabsCalled) return;
+        mRestoreTabsCalled = true;
+        assert !setActiveTab : "Cannot set active tab on archived tabs.";
+        super.restoreTabs(setActiveTab);
+    }
+
+    @Override
+    public void cleanupInstance(int instanceId) {
+        assert false : "Not reached.";
+    }
+
+    /** Returns the {@link TabCreator} for archived tabs. */
+    public TabCreator getArchivedTabCreator() {
+        return mArchivedTabCreatorManager.getTabCreator(false);
+    }
+
+    /** Returns the {@link TabArchiver}. */
+    public TabArchiver getTabArchiver() {
+        return mTabArchiver;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabbedModeTabModelOrchestrator.java b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabbedModeTabModelOrchestrator.java
index 28d328d..bc2eaca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabbedModeTabModelOrchestrator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabbedModeTabModelOrchestrator.java
@@ -9,15 +9,20 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import org.chromium.base.Callback;
 import org.chromium.base.supplier.OneshotSupplier;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.multiwindow.MultiInstanceManager;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileProvider;
+import org.chromium.chrome.browser.tab_ui.TabContentManager;
 import org.chromium.chrome.browser.tabmodel.MismatchedIndicesHandler;
 import org.chromium.chrome.browser.tabmodel.NextTabPolicy.NextTabPolicySupplier;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorBase;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl;
 import org.chromium.chrome.browser.tabmodel.TabPersistentStore;
 import org.chromium.chrome.browser.tabmodel.TabbedModeTabPersistencePolicy;
 import org.chromium.ui.widget.Toast;
@@ -29,8 +34,14 @@
 public class TabbedModeTabModelOrchestrator extends TabModelOrchestrator {
     private final boolean mTabMergingEnabled;
 
+    // This class is driven by TabbedModeTabModelOrchestrator to prevent duplicate glue code in
+    //  ChromeTabbedActivity.
+    private ArchivedTabModelOrchestrator mArchivedTabModelOrchestrator;
+    private OneshotSupplier<ProfileProvider> mProfileProviderSupplier;
+
     /**
      * Constructor.
+     *
      * @param tabMergingEnabled Whether we are on the platform where tab merging is enabled.
      */
     public TabbedModeTabModelOrchestrator(boolean tabMergingEnabled) {
@@ -40,6 +51,12 @@
     /**
      * Creates the TabModelSelector and the TabPersistentStore.
      *
+     * @param activity The activity that hosts this TabModelOrchestrator.
+     * @param profileProviderSupplier Supplies the {@link ProfileProvider} for the activity.
+     * @param tabCreatorManager Manager for the {@link TabCreator} for the {@link TabModelSelector}.
+     * @param nextTabPolicyProvider Policy for what to do when a tab is closed.
+     * @param mismatchedIndicesHandler Handles when indices are mismatched.
+     * @param selectorIndex Which index to use when requesting a selector.
      * @return Whether the creation was successful. It may fail is we reached the limit of number of
      *     windows.
      */
@@ -50,6 +67,7 @@
             NextTabPolicySupplier nextTabPolicySupplier,
             MismatchedIndicesHandler mismatchedIndicesHandler,
             int selectorIndex) {
+        mProfileProviderSupplier = profileProviderSupplier;
         boolean mergeTabsOnStartup = shouldMergeTabs(activity);
         if (mergeTabsOnStartup) {
             MultiInstanceManager.mergedOnStartup();
@@ -135,6 +153,53 @@
         mTabPersistentStore.cleanupStateFile(instanceId);
     }
 
+    @Override
+    public void onNativeLibraryReady(TabContentManager tabContentManager) {
+        super.onNativeLibraryReady(tabContentManager);
+
+        if (ChromeFeatureList.sAndroidTabDeclutter.isEnabled()) {
+            // The profile will be available because native is initialized.
+            assert mProfileProviderSupplier.hasValue();
+
+            Profile profile = mProfileProviderSupplier.get().getOriginalProfile();
+            mArchivedTabModelOrchestrator = ArchivedTabModelOrchestrator.getForProfile(profile);
+            mArchivedTabModelOrchestrator.maybeCreateTabModels();
+            mArchivedTabModelOrchestrator.onNativeLibraryReady(tabContentManager);
+        }
+    }
+
+    @Override
+    public void loadState(
+            boolean ignoreIncognitoFiles, Callback<String> onStandardActiveIndexRead) {
+        super.loadState(ignoreIncognitoFiles, onStandardActiveIndexRead);
+
+        if (ChromeFeatureList.sAndroidTabDeclutter.isEnabled()) {
+            assert mArchivedTabModelOrchestrator != null;
+            mArchivedTabModelOrchestrator.loadState(
+                    /* ignoreIncognitoFiles= */ true, /* onStandardActiveIndexRead= */ null);
+        }
+    }
+
+    @Override
+    public void restoreTabs(boolean setActiveTab) {
+        super.restoreTabs(setActiveTab);
+
+        if (ChromeFeatureList.sAndroidTabDeclutter.isEnabled()) {
+            assert mArchivedTabModelOrchestrator != null;
+            mArchivedTabModelOrchestrator.restoreTabs(/* setActiveTab= */ false);
+        }
+    }
+
+    @Override
+    public void saveState() {
+        super.saveState();
+
+        if (ChromeFeatureList.sAndroidTabDeclutter.isEnabled()) {
+            assert mArchivedTabModelOrchestrator != null;
+            mArchivedTabModelOrchestrator.saveState();
+        }
+    }
+
     public TabPersistentStore getTabPersistentStoreForTesting() {
         return mTabPersistentStore;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditor.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditor.java
index 6f74dace..190d259 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditor.java
@@ -13,6 +13,8 @@
 import android.widget.Button;
 import android.widget.EditText;
 
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StringRes;
 import androidx.annotation.VisibleForTesting;
 import androidx.fragment.app.Fragment;
 
@@ -30,12 +32,13 @@
 import org.chromium.components.autofill.IbanRecordType;
 
 /**
- * This class creates a view for adding a local IBAN. A local IBAN gets saved to the user's device
- * only.
+ * This class creates a view for adding and editing a local IBAN. A local IBAN gets saved to the
+ * user's device only.
  */
 public class AutofillLocalIbanEditor extends AutofillEditorBase implements ProfileDependentSetting {
     private static Callback<Fragment> sObserverForTest;
 
+    protected Iban mIban;
     protected Button mDoneButton;
     protected EditText mNickname;
     protected TextInputLayout mNicknameLabel;
@@ -52,6 +55,9 @@
         // TODO(b/309163678): Disable autofill for the fields.
         View v = super.onCreateView(inflater, container, savedInstanceState);
 
+        PersonalDataManager personalDataManager =
+                PersonalDataManagerFactory.getForProfile(mProfile);
+        mIban = personalDataManager.getIban(mGUID);
         mDoneButton = (Button) v.findViewById(R.id.button_primary);
         mNickname = (EditText) v.findViewById(R.id.iban_nickname_edit);
         mNicknameLabel = (TextInputLayout) v.findViewById(R.id.iban_nickname_label);
@@ -60,6 +66,7 @@
         mNickname.setOnFocusChangeListener(
                 (view, hasFocus) -> mNicknameLabel.setCounterEnabled(hasFocus));
 
+        addIbanDataToEditFields();
         initializeButtons(v);
         if (sObserverForTest != null) {
             sObserverForTest.onResult(this);
@@ -68,15 +75,13 @@
     }
 
     @Override
-    protected int getLayoutId() {
+    protected @LayoutRes int getLayoutId() {
         return R.layout.autofill_local_iban_editor;
     }
 
     @Override
-    protected int getTitleResourceId(boolean isNewEntry) {
-        // TODO(b/309163678): Use isNewEntry to decide which title to display
-        // (i.e., autofill_add_local_iban or autofill_edit_local_iban).
-        return R.string.autofill_add_local_iban;
+    protected @StringRes int getTitleResourceId(boolean isNewEntry) {
+        return isNewEntry ? R.string.autofill_add_local_iban : R.string.autofill_edit_local_iban;
     }
 
     @Override
@@ -91,12 +96,17 @@
 
     @Override
     protected boolean saveEntry() {
+        // If an existing local IBAN is being edited, its GUID and record type are set here. In the
+        // case of a new IBAN, these values are set right before being written to the autofill
+        // table.
         Iban iban =
                 Iban.create(
-                        /* guid= */ "",
+                        /* guid= */ mGUID,
                         /* label= */ "",
                         /* nickname= */ mNickname.getText().toString().trim(),
-                        /* recordType= */ IbanRecordType.UNKNOWN,
+                        /* recordType= */ mGUID.isEmpty()
+                                ? IbanRecordType.UNKNOWN
+                                : IbanRecordType.LOCAL_IBAN,
                         /* value= */ mValue.getText().toString());
         PersonalDataManager personalDataManager =
                 PersonalDataManagerFactory.getForProfile(mProfile);
@@ -114,6 +124,7 @@
     @Override
     protected void initializeButtons(View v) {
         super.initializeButtons(v);
+        mNickname.addTextChangedListener(this);
         mValue.addTextChangedListener(this);
     }
 
@@ -122,6 +133,19 @@
         sObserverForTest = observerForTest;
     }
 
+    private void addIbanDataToEditFields() {
+        if (mIban == null) {
+            return;
+        }
+
+        if (!mIban.getNickname().isEmpty()) {
+            mNickname.setText(mIban.getNickname());
+        }
+        if (!mIban.getValue().isEmpty()) {
+            mValue.setText(mIban.getValue());
+        }
+    }
+
     private void updateSaveButtonEnabled() {
         // Enable save button if IBAN value is valid.
         mDoneButton.setEnabled(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
index 8100a18..922d5c0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
@@ -1790,9 +1790,13 @@
 
             node.setBoundsInParent(rectToPx(mTouchTarget));
             node.setContentDescription(view.getAccessibilityDescription());
-            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+            if (view.hasClickAction()) {
+                node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+            }
             node.addAction(AccessibilityNodeInfoCompat.ACTION_FOCUS);
-            node.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
+            if (view.hasLongClickAction()) {
+                node.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
+            }
         }
 
         private Rect rectToPx(RectF rect) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutGroupTitle.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutGroupTitle.java
index 696903e..d269327 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutGroupTitle.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutGroupTitle.java
@@ -161,6 +161,18 @@
     }
 
     @Override
+    public boolean hasClickAction() {
+        // TODO(https://crbug.com/326492955): Implement click to collapse/expand.
+        return false;
+    }
+
+    @Override
+    public boolean hasLongClickAction() {
+        // TODO(https://crbug.com/333777015): Implement long press to drag tab group.
+        return false;
+    }
+
+    @Override
     public void handleClick(long time) {
         // TODO(crbug.com/326492955): Implement click to collapse/expand.
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
index b6375050..b624e0a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
@@ -2745,7 +2745,7 @@
 
         // 4. Calculate the realistic tab width.
         mCachedTabWidth = MathUtils.clamp(optimalTabWidth, mMinTabWidth, mMaxTabWidth);
-        mHalfTabWidth = mCachedTabWidth / 2;
+        mHalfTabWidth = (mCachedTabWidth - mTabOverlapWidth) * REORDER_OVERLAP_SWITCH_PERCENTAGE;
 
         // 5. Prepare animations and propagate width to all tabs.
         finishAnimationsAndPushTabUpdates();
@@ -4109,7 +4109,7 @@
             int curIndexInStripTab, boolean isInGroup, boolean towardEnd) {
         int curIndexInStripView = findStripViewIndexForStripTab(curIndexInStripTab);
         float dragOutThreshold = mHalfTabWidth * REORDER_OVERLAP_SWITCH_PERCENTAGE;
-        float dragInThreshold = mHalfTabWidth - mTabOverlapWidth;
+        float dragInThreshold = mHalfTabWidth;
 
         assert curIndexInStripView != TabModel.INVALID_TAB_INDEX;
         if (isInGroup) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
index 523593e..d5d9218 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
@@ -7,11 +7,9 @@
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Intent;
-import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -29,14 +27,12 @@
 import org.chromium.base.supplier.OneShotCallback;
 import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.UnownedUserDataSupplier;
-import org.chromium.blink.mojom.DisplayMode;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.app.tabmodel.TabWindowManagerSingleton;
 import org.chromium.chrome.browser.back_press.BackPressManager;
 import org.chromium.chrome.browser.browserservices.intents.WebappConstants;
 import org.chromium.chrome.browser.content.WebContentsFactory;
-import org.chromium.chrome.browser.contextmenu.ContextMenuPopulatorFactory;
 import org.chromium.chrome.browser.crash.ChromePureJavaExceptionReporter;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
@@ -56,7 +52,6 @@
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
 import org.chromium.chrome.browser.password_manager.ManagePasswordsReferrer;
 import org.chromium.chrome.browser.password_manager.PasswordManagerLauncher;
-import org.chromium.chrome.browser.pdf.PdfInfo;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
 import org.chromium.chrome.browser.profiles.OTRProfileID;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -65,21 +60,16 @@
 import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabBuilder;
-import org.chromium.chrome.browser.tab.TabDelegateFactory;
 import org.chromium.chrome.browser.tab.TabLaunchType;
-import org.chromium.chrome.browser.tab.TabWebContentsDelegateAndroid;
 import org.chromium.chrome.browser.toolbar.VoiceToolbarButtonController;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager.SnackbarManageable;
-import org.chromium.chrome.browser.ui.native_page.NativePage;
 import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityClient.IntentOrigin;
 import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityClient.SearchType;
 import org.chromium.chrome.browser.ui.system.StatusBarColorController;
 import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
-import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate;
 import org.chromium.components.browser_ui.widget.InsetObserver;
 import org.chromium.components.browser_ui.widget.InsetObserverSupplier;
-import org.chromium.components.external_intents.ExternalNavigationHandler;
 import org.chromium.components.metrics.OmniboxEventProtos.OmniboxEventProto.PageClassification;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.WebContents;
@@ -141,11 +131,6 @@
 
         /** Called when {@link SearchActivity#finishDeferredInitialization} is done. */
         void onFinishDeferredInitialization() {}
-
-        /** Returning true causes the Activity to finish itself immediately when starting up. */
-        boolean isActivityDisabledForTests() {
-            return false;
-        }
     }
 
     private static final Object DELEGATE_LOCK = new Object();
@@ -153,11 +138,6 @@
     /** Notified about events happening for the SearchActivity. */
     private static SearchActivityDelegate sDelegate;
 
-    /** Main content view. */
-    private ViewGroup mContentView;
-
-    private View mAnchorView;
-
     // Incoming intent request type. See {@link SearchActivityUtils#IntentOrigin}.
     @IntentOrigin Integer mIntentOrigin;
     // Incoming intent search type. See {@link SearchActivityUtils#SearchType}.
@@ -169,10 +149,9 @@
     /** Input submitted before before the native library was loaded. */
     private OmniboxLoadUrlParams mQueuedParams;
 
-    /** The View that represents the search box. */
+    private LocationBarCoordinator mLocationBarCoordinator;
     private SearchActivityLocationBarLayout mSearchBox;
-
-    LocationBarCoordinator mLocationBarCoordinator;
+    private View mAnchorView;
 
     private SnackbarManager mSnackbarManager;
     private Tab mTab;
@@ -194,12 +173,6 @@
     }
 
     @Override
-    protected boolean isStartedUpCorrectly(Intent intent) {
-        if (getActivityDelegate().isActivityDisabledForTests()) return false;
-        return super.isStartedUpCorrectly(intent);
-    }
-
-    @Override
     protected boolean shouldDelayBrowserStartup() {
         return true;
     }
@@ -240,14 +213,14 @@
         mInsetObserverViewSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
         mInsetObserverViewSupplier.set(new InsetObserver(rootView, true));
 
-        mContentView = createContentView();
-        setContentView(mContentView);
+        var contentView = createContentView();
+        setContentView(contentView);
 
         // Build the search box.
         mSearchBox =
                 (SearchActivityLocationBarLayout)
-                        mContentView.findViewById(R.id.search_location_bar);
-        mAnchorView = mContentView.findViewById(R.id.toolbar);
+                        contentView.findViewById(R.id.search_location_bar);
+        mAnchorView = contentView.findViewById(R.id.toolbar);
 
         // Create status bar color controller and assign to search activity.
         if (OmniboxFeatures.shouldMatchToolbarAndStatusBarColor()) {
@@ -443,72 +416,13 @@
 
     private void finishNativeInitializationWithProfile(Profile profile) {
         refinePageClassWithProfile(profile);
-        TabDelegateFactory factory =
-                new TabDelegateFactory() {
-                    @Override
-                    public TabWebContentsDelegateAndroid createWebContentsDelegate(Tab tab) {
-                        return new TabWebContentsDelegateAndroid() {
-                            @Override
-                            public int getDisplayMode() {
-                                return DisplayMode.BROWSER;
-                            }
-
-                            @Override
-                            protected boolean shouldResumeRequestsForCreatedWindow() {
-                                return false;
-                            }
-
-                            @Override
-                            protected boolean addNewContents(
-                                    WebContents sourceWebContents,
-                                    WebContents webContents,
-                                    int disposition,
-                                    Rect initialPosition,
-                                    boolean userGesture) {
-                                return false;
-                            }
-
-                            @Override
-                            protected void setOverlayMode(boolean useOverlayMode) {}
-
-                            @Override
-                            public boolean canShowAppBanners() {
-                                return false;
-                            }
-                        };
-                    }
-
-                    @Override
-                    public ExternalNavigationHandler createExternalNavigationHandler(Tab tab) {
-                        return null;
-                    }
-
-                    @Override
-                    public ContextMenuPopulatorFactory createContextMenuPopulatorFactory(Tab tab) {
-                        return null;
-                    }
-
-                    @Override
-                    public BrowserControlsVisibilityDelegate
-                            createBrowserControlsVisibilityDelegate(Tab tab) {
-                        return null;
-                    }
-
-                    @Override
-                    public NativePage createNativePage(
-                            String url, NativePage candidatePage, Tab tab, PdfInfo pdfInfo) {
-                        // SearchActivity does not create native pages.
-                        return null;
-                    }
-                };
-
         WebContents webContents = WebContentsFactory.createWebContents(profile, false, false);
         mTab =
                 new TabBuilder(profile)
                         .setWindow(getWindowAndroid())
                         .setLaunchType(TabLaunchType.FROM_EXTERNAL_APP)
                         .setWebContents(webContents)
-                        .setDelegateFactory(factory)
+                        .setDelegateFactory(new SearchActivityTabDelegateFactory())
                         .build();
         mTab.loadUrl(new LoadUrlParams(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL));
 
@@ -692,20 +606,11 @@
                 .recordLocaleBasedSearchMetrics(true, params.url, params.transitionType);
     }
 
-    private ViewGroup createContentView() {
-        assert mContentView == null;
-
-        ViewGroup contentView =
-                (ViewGroup)
-                        LayoutInflater.from(this).inflate(R.layout.search_activity, null, false);
-        contentView.setOnClickListener(
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        cancelSearch();
-                    }
-                });
-
+    @VisibleForTesting
+    /* package */ ViewGroup createContentView() {
+        var contentView =
+                (ViewGroup) getLayoutInflater().inflate(R.layout.search_activity, null, false);
+        contentView.setOnClickListener(v -> cancelSearch());
         return contentView;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
index 19e8ebc4..9b42b86 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
@@ -243,7 +243,6 @@
 
                     mUrlBar.requestFocus();
                     mUrlCoordinator.setKeyboardVisibility(true, false);
-                    mAutocompleteCoordinator.startCachedZeroSuggest();
                 });
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityTabDelegateFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityTabDelegateFactory.java
new file mode 100644
index 0000000..325184d
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityTabDelegateFactory.java
@@ -0,0 +1,86 @@
+// 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.searchwidget;
+
+import android.graphics.Rect;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.blink.mojom.DisplayMode;
+import org.chromium.chrome.browser.contextmenu.ContextMenuPopulatorFactory;
+import org.chromium.chrome.browser.pdf.PdfInfo;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabDelegateFactory;
+import org.chromium.chrome.browser.tab.TabWebContentsDelegateAndroid;
+import org.chromium.chrome.browser.ui.native_page.NativePage;
+import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate;
+import org.chromium.components.external_intents.ExternalNavigationHandler;
+import org.chromium.content_public.browser.WebContents;
+
+/**
+ * Minimalistic implementation of a {@link TabDelegateFactory} that creates trivial,
+ * non-interactable tabs.
+ */
+class SearchActivityTabDelegateFactory implements TabDelegateFactory {
+    @VisibleForTesting
+    /* package */ static class WebContentsDelegate extends TabWebContentsDelegateAndroid {
+        @Override
+        public int getDisplayMode() {
+            return DisplayMode.BROWSER;
+        }
+
+        @Override
+        @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+        public boolean shouldResumeRequestsForCreatedWindow() {
+            return false;
+        }
+
+        @Override
+        @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+        public boolean addNewContents(
+                WebContents sourceWebContents,
+                WebContents webContents,
+                int disposition,
+                Rect initialPosition,
+                boolean userGesture) {
+            return false;
+        }
+
+        @Override
+        @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+        public void setOverlayMode(boolean useOverlayMode) {}
+
+        @Override
+        public boolean canShowAppBanners() {
+            return false;
+        }
+    }
+
+    @Override
+    public TabWebContentsDelegateAndroid createWebContentsDelegate(Tab tab) {
+        return new WebContentsDelegate();
+    }
+
+    @Override
+    public ExternalNavigationHandler createExternalNavigationHandler(Tab tab) {
+        return null;
+    }
+
+    @Override
+    public ContextMenuPopulatorFactory createContextMenuPopulatorFactory(Tab tab) {
+        return null;
+    }
+
+    @Override
+    public BrowserControlsVisibilityDelegate createBrowserControlsVisibilityDelegate(Tab tab) {
+        return null;
+    }
+
+    @Override
+    public NativePage createNativePage(
+            String url, NativePage candidatePage, Tab tab, PdfInfo pdfInfo) {
+        return null;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
index 278b35f..bc3b1d7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
@@ -122,4 +122,11 @@
     "+chrome/android/java/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTabHelper.java",
     "+chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtils.java",
   ],
+  'TabArchiver\.java': [
+    "+chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ReparentingTask.java",
+    "+chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/AsyncTabParamsManager.java",
+    "+chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabCreator.java",
+    "+chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModel.java",
+    "+chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabReparentingParams.java",
+  ]
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiveSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiveSettings.java
new file mode 100644
index 0000000..bdc782e
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiveSettings.java
@@ -0,0 +1,88 @@
+// 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.tab;
+
+import org.chromium.base.shared_preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+
+/** Class to manage reading/writing preferences related to tab declutter. */
+public class TabArchiveSettings {
+    static final boolean ARCHIVE_ENABLED_DEFAULT = true;
+    static final boolean AUTO_DELETE_ENABLED_DEFAULT = true;
+    static final int ARCHIVE_TIME_DELTA_HOURS_DEFAULT = 7 * 24;
+    static final int AUTO_DELETE_TIME_DELTA_HOURS_DEFAULT = 60 * 24;
+
+    private final SharedPreferencesManager mPrefsManager;
+
+    /**
+     * Constructor.
+     *
+     * @param prefsManager The {@link SharedPreferencesManager} used to read/write settings.
+     */
+    public TabArchiveSettings(SharedPreferencesManager prefsManager) {
+        mPrefsManager = prefsManager;
+    }
+
+    /** Returns whether archive is enabled in settings. */
+    public boolean getArchiveEnabled() {
+        return mPrefsManager.readBoolean(
+                ChromePreferenceKeys.TAB_DECLUTTER_ARCHIVE_ENABLED, ARCHIVE_ENABLED_DEFAULT);
+    }
+
+    /** Sets whether archive is enabled in settings. */
+    public void setArchiveEnabled(boolean enabled) {
+        mPrefsManager.writeBoolean(ChromePreferenceKeys.TAB_DECLUTTER_ARCHIVE_ENABLED, enabled);
+    }
+
+    /** Returns the time delta used to determine if a tab is eligible for archive. */
+    public int getArchiveTimeDeltaHours() {
+        return mPrefsManager.readInt(
+                ChromePreferenceKeys.TAB_DECLUTTER_ARCHIVE_TIME_DELTA_HOURS,
+                getDefaultArchiveTimeDeltaHours());
+    }
+
+    /** Sets the time delta used to determine if a tab is eligible for archive. */
+    public void setArchiveTimeDeltaHours(int timeDeltaHours) {
+        mPrefsManager.writeInt(
+                ChromePreferenceKeys.TAB_DECLUTTER_ARCHIVE_TIME_DELTA_HOURS, timeDeltaHours);
+    }
+
+    /** Returns whether auto-deletion of archived tabs is enabled. */
+    public boolean isAutoDeleteEnabled() {
+        return mPrefsManager.readBoolean(
+                ChromePreferenceKeys.TAB_DECLUTTER_AUTO_DELETE_ENABLED,
+                AUTO_DELETE_ENABLED_DEFAULT);
+    }
+
+    /** Sets whether auto deletion for archived tabs is enabled in settings. */
+    public void setAutoDeleteEnabled(boolean enabled) {
+        mPrefsManager.writeBoolean(ChromePreferenceKeys.TAB_DECLUTTER_AUTO_DELETE_ENABLED, enabled);
+    }
+
+    /**
+     * Returns the time delta used to determine if an archived tab is eligible for auto deletion.
+     */
+    public int getAutoDeleteTimeDeltaHours() {
+        return mPrefsManager.readInt(
+                ChromePreferenceKeys.TAB_DECLUTTER_AUTO_DELETE_TIME_DELTA_HOURS,
+                getDefaultAutoDeleteTimeDeltaHours());
+    }
+
+    /** Sets the time delta used to determine if an archived tab is eligible for auto deletion. */
+    public void setAutoDeleteTimeDeltaHours(int timeDeltaHours) {
+        mPrefsManager.writeInt(
+                ChromePreferenceKeys.TAB_DECLUTTER_AUTO_DELETE_TIME_DELTA_HOURS, timeDeltaHours);
+    }
+
+    private int getDefaultArchiveTimeDeltaHours() {
+        // TODO(crbug.com/332942593): Pull default from finch param.
+        return ARCHIVE_TIME_DELTA_HOURS_DEFAULT;
+    }
+
+    private int getDefaultAutoDeleteTimeDeltaHours() {
+        // TODO(crbug.com/332942593): Pull default from finch param.
+        return AUTO_DELETE_TIME_DELTA_HOURS_DEFAULT;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiver.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiver.java
new file mode 100644
index 0000000..23f464a4
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiver.java
@@ -0,0 +1,63 @@
+// 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.tab;
+
+import static org.chromium.chrome.browser.tabmodel.TabList.INVALID_TAB_INDEX;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.chrome.browser.tabmodel.AsyncTabParamsManager;
+import org.chromium.chrome.browser.tabmodel.TabCreator;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabReparentingParams;
+
+/** Responsible for moving tabs to/from the archived {@link TabModel}. */
+public class TabArchiver {
+    private final TabCreator mArchivedTabCreator;
+    private final TabModel mArchivedTabModel;
+    private final AsyncTabParamsManager mAsyncTabParamsManager;
+
+    /**
+     * @param archivedTabCreator The {@link TabCreator} for the archived TabModel.
+     * @param archivedTabModel The {@link TabModel} for archived tabs.
+     * @param asyncTabParamsManager The {@link AsyncTabParamsManager} used when unarchiving tabs.
+     */
+    public TabArchiver(
+            TabCreator archivedTabCreator,
+            TabModel archivedTabModel,
+            AsyncTabParamsManager asyncTabParamsManager) {
+        mArchivedTabCreator = archivedTabCreator;
+        mArchivedTabModel = archivedTabModel;
+        mAsyncTabParamsManager = asyncTabParamsManager;
+    }
+
+    /**
+     * Create an archived copy of the given Tab in the archived TabModel, and close the Tab in the
+     * regular TabModel. Must be called on the UI thread.
+     *
+     * @param tabModel The {@link TabModel} the tab currently belongs to.
+     * @param tab The {@link Tab} to unarchive.
+     */
+    public void archiveAndRemoveTab(TabModel tabModel, Tab tab) {
+        ThreadUtils.assertOnUiThread();
+        TabState tabState = TabStateExtractor.from(tab);
+        mArchivedTabCreator.createFrozenTab(tabState, tab.getId(), INVALID_TAB_INDEX);
+        tabModel.closeTab(tab);
+    }
+
+    /**
+     * Unarchive the given tab, moving it into the normal TabModel. The tab is reused between the
+     * archived/regular TabModels. Must be called on the UI thread.
+     *
+     * @param tabCreator The {@link TabCreator} to use when recreating the tabs.
+     * @param tab The {@link Tab} to unarchive.
+     */
+    public void unarchiveAndRestoreTab(TabCreator tabCreator, Tab tab) {
+        ThreadUtils.assertOnUiThread();
+        TabState tabState = TabStateExtractor.from(tab);
+        mArchivedTabModel.removeTab(tab);
+        mAsyncTabParamsManager.add(tab.getId(), new TabReparentingParams(tab, null));
+        tabCreator.createFrozenTab(tabState, tab.getId(), INVALID_TAB_INDEX);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreator.java
new file mode 100644
index 0000000..025bace3
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreator.java
@@ -0,0 +1,100 @@
+// 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.tabmodel;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.chromium.chrome.browser.customtabs.CustomTabDelegateFactory;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
+import org.chromium.chrome.browser.tab.TabCreationState;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tab.TabState;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.url.GURL;
+
+/**
+ * Creates tabs for the archived tab model selector during restore. This only creates frozen tabs.
+ */
+public class ArchivedTabCreator extends TabCreator {
+    private final WindowAndroid mWindow;
+    private TabModel mTabModel;
+
+    /**
+     * @param window The {@link AndroidWindow} to attach tabs to.
+     */
+    public ArchivedTabCreator(WindowAndroid window) {
+        mWindow = window;
+    }
+
+    /**
+     * @param tabModel The {@link TabModel} to add tabs to.
+     */
+    public void setTabModel(TabModel tabModel) {
+        mTabModel = tabModel;
+    }
+
+    @Override
+    public @Nullable Tab createNewTab(
+            LoadUrlParams loadUrlParams, @TabLaunchType int type, Tab parent) {
+        return createNewTab(loadUrlParams, type, parent, TabList.INVALID_TAB_INDEX);
+    }
+
+    @Override
+    public @Nullable Tab createNewTab(
+            LoadUrlParams loadUrlParams, @TabLaunchType int type, Tab parent, int index) {
+        // TODO(crbug.com/331827001): Also possible to change the entire restore path.
+        assert type == TabLaunchType.FROM_RESTORE
+                : "ArchivedTabCreator only supports #createNewTab calls as a restore fallback.";
+        Tab tab =
+                TabBuilder.createForLazyLoad(mTabModel.getProfile(), loadUrlParams)
+                        .setWindow(mWindow)
+                        .setLaunchType(TabLaunchType.FROM_RESTORE)
+                        .setTabResolver((tabId) -> TabModelUtils.getTabById(mTabModel, tabId))
+                        .setInitiallyHidden(true)
+                        .setDelegateFactory(CustomTabDelegateFactory.createEmpty())
+                        .build();
+        mTabModel.addTab(
+                tab, index, TabLaunchType.FROM_RESTORE, TabCreationState.FROZEN_FOR_LAZY_LOAD);
+        return tab;
+    }
+
+    @Override
+    public Tab createFrozenTab(TabState state, int id, int index) {
+        assert mTabModel != null : "Creating frozen tab before native library initialized.";
+        Tab tab =
+                TabBuilder.createFromFrozenState(mTabModel.getProfile())
+                        .setWindow(mWindow)
+                        .setId(id)
+                        .setLaunchType(TabLaunchType.FROM_RESTORE)
+                        .setTabResolver((tabId) -> TabModelUtils.getTabById(mTabModel, tabId))
+                        .setInitiallyHidden(true)
+                        .setTabState(state)
+                        .setDelegateFactory(CustomTabDelegateFactory.createEmpty())
+                        .build();
+        mTabModel.addTab(
+                tab, index, TabLaunchType.FROM_RESTORE, TabCreationState.FROZEN_FOR_LAZY_LOAD);
+        return tab;
+    }
+
+    @Override
+    public @Nullable Tab launchUrl(String url, @TabLaunchType int type) {
+        assert false : "Not reached.";
+        return null;
+    }
+
+    @Override
+    public boolean createTabWithWebContents(
+            @Nullable Tab parent,
+            WebContents webContents,
+            @TabLaunchType int type,
+            @NonNull GURL url) {
+        assert false : "Not reached.";
+        return false;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabModelSelectorImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabModelSelectorImpl.java
new file mode 100644
index 0000000..f9aeb53
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabModelSelectorImpl.java
@@ -0,0 +1,157 @@
+// 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.tabmodel;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.chrome.browser.flags.ActivityType;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabSelectionType;
+import org.chromium.chrome.browser.tab_ui.TabContentManager;
+import org.chromium.chrome.browser.tabmodel.NextTabPolicy.NextTabPolicySupplier;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.url.GURL;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** {@link TabModelSelector} for archived tabs. Must be instantiated and used on the UI thread. */
+public class ArchivedTabModelSelectorImpl extends TabModelSelectorBase implements TabModelDelegate {
+    /** Flag set to false when the asynchronous loading of tabs is finished. */
+    private final AtomicBoolean mSessionRestoreCompleted = new AtomicBoolean(true);
+
+    private final Profile mProfile;
+    private final TabModelOrderController mOrderController;
+    private final NextTabPolicySupplier mNextTabPolicySupplier;
+    private final AsyncTabParamsManager mAsyncTabParamsManager;
+
+    private TabContentManager mTabContentManager;
+
+    /**
+     * Builds a {@link ArchivedTabModelSelectorImpl} instance.
+     *
+     * @param profile The {@link Profile} used.
+     * @param tabCreatorManager A {@link TabCreatorManager} instance.
+     * @param tabModelFilterFactory The factory for setting up {@link TabModelFilter}s.
+     * @param nextTabPolicySupplier A policy for next tab selection.
+     * @param asyncTabParamsManager Manager of async params for reparenting.
+     */
+    public ArchivedTabModelSelectorImpl(
+            Profile profile,
+            TabCreatorManager tabCreatorManager,
+            TabModelFilterFactory tabModelFilterFactory,
+            NextTabPolicySupplier nextTabPolicySupplier,
+            AsyncTabParamsManager asyncTabParamsManager) {
+        super(tabCreatorManager, tabModelFilterFactory, /* startIncognito= */ false);
+        mProfile = profile;
+        mOrderController = new TabModelOrderControllerImpl(this);
+        mNextTabPolicySupplier = nextTabPolicySupplier;
+        mAsyncTabParamsManager = asyncTabParamsManager;
+    }
+
+    @Override
+    public void markTabStateInitialized() {
+        super.markTabStateInitialized();
+        if (!mSessionRestoreCompleted.getAndSet(false)) return;
+
+        // This is the first time we set
+        // |mSessionRestoreCompleted|, so we need to broadcast.
+        TabModelImpl model = (TabModelImpl) getModel(false);
+        model.broadcastSessionRestoreComplete();
+    }
+
+    /**
+     * Should be called once the native library is loaded so that the actual internals of this class
+     * can be initialized.
+     *
+     * @param tabContentProvider A {@link TabContentManager} instance.
+     */
+    @Override
+    public void onNativeLibraryReady(TabContentManager tabContentProvider) {
+        assert mTabContentManager == null : "onNativeLibraryReady called twice!";
+
+        TabCreator tabCreator = getTabCreatorManager().getTabCreator(false);
+        // TODO(crbug.com/331688951): Consider using a custom TabModel.
+        TabModelImpl normalModel =
+                new TabModelImpl(
+                        mProfile,
+                        ActivityType.TABBED,
+                        tabCreator,
+                        /* incognitoTabCreator= */ null,
+                        mOrderController,
+                        tabContentProvider,
+                        mNextTabPolicySupplier,
+                        mAsyncTabParamsManager,
+                        this,
+                        /* supportUndo= */ true);
+        ((ArchivedTabCreator) tabCreator).setTabModel(normalModel);
+
+        onNativeLibraryReadyInternal(
+                tabContentProvider,
+                normalModel,
+                EmptyTabModel.getInstance(/* isIncognito= */ true));
+    }
+
+    @VisibleForTesting
+    void onNativeLibraryReadyInternal(
+            TabContentManager tabContentProvider,
+            TabModel normalModel,
+            IncognitoTabModel incognitoModel) {
+        mTabContentManager = tabContentProvider;
+        initialize(normalModel, incognitoModel);
+
+        new TabModelSelectorTabObserver(this) {
+            @Override
+            public void onPageLoadStarted(Tab tab, GURL url) {
+                assert false : "Tabs in the archived tab model should not be navigated.";
+            }
+
+            @Override
+            public void onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window) {
+                if (window == null && !isReparentingInProgress()) {
+                    getModel(tab.isIncognito()).removeTab(tab);
+                }
+            }
+        };
+    }
+
+    @Override
+    public void openMostRecentlyClosedEntry(TabModel tabModel) {
+        assert false : "Not reached.";
+    }
+
+    /**
+     * Exposed to allow tests to initialize the selector with different tab models.
+     *
+     * @param normalModel The normal tab model.
+     * @param incognitoModel The incognito tab model.
+     */
+    public void initializeForTesting(TabModel normalModel, IncognitoTabModel incognitoModel) {
+        initialize(normalModel, incognitoModel);
+    }
+
+    @Override
+    public void selectModel(boolean incognito) {
+        assert !incognito : "The archived tab model selector has no incognito mode.";
+        assert !getCurrentModel().isIncognito()
+                : "The incognito model of the archived tab model selector was selected.";
+        // Intentional no-op.
+    }
+
+    @Override
+    public void requestToShowTab(Tab tab, @TabSelectionType int type) {
+        // Intentional noop.
+    }
+
+    private void cacheTabBitmap(Tab tabToCache) {
+        // Intentional noop.
+    }
+
+    @Override
+    public boolean isSessionRestoreInProgress() {
+        return mSessionRestoreCompleted.get();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
index 158ea8e..93b8f9f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
@@ -865,6 +865,11 @@
             return;
         }
 
+        if (mSaveTabTask != null && mSaveTabTask.mId == tab.getId()) {
+            RecordHistogram.recordCount100Histogram(
+                    "Tabs.PotentialDoubleDirty.SaveQueueSize", mTabsToSave.size());
+        }
+
         mTabsToSave.addLast(tab);
     }
 
@@ -1345,6 +1350,7 @@
         @Override
         protected void onPreExecute() {
             if (mDestroyed || isCancelled()) return;
+            TabStateAttributes.from(mTab).clearTabStateDirtiness();
             mState = TabStateExtractor.from(mTab);
         }
 
@@ -1357,9 +1363,6 @@
         @Override
         protected void onPostExecute(Void v) {
             if (mDestroyed || isCancelled()) return;
-            if (mStateSaved) {
-                if (!mTab.isDestroyed()) TabStateAttributes.from(mTab).clearTabStateDirtiness();
-            }
             mSaveTabTask = null;
             saveNextTab();
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabArchiverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabArchiverTest.java
new file mode 100644
index 0000000..6c40787
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabArchiverTest.java
@@ -0,0 +1,110 @@
+// 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;
+
+import static org.junit.Assert.assertEquals;
+
+import static org.chromium.base.ThreadUtils.runOnUiThreadBlocking;
+import static org.chromium.base.ThreadUtils.runOnUiThreadBlockingNoException;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.Features.EnableFeatures;
+import org.chromium.chrome.browser.app.tabmodel.ArchivedTabModelOrchestrator;
+import org.chromium.chrome.browser.app.tabmodel.AsyncTabParamsManagerSingleton;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabArchiver;
+import org.chromium.chrome.browser.tabmodel.TabCreator;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.EmbeddedTestServerRule;
+
+/** Tests for TabArchiver. */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@Batch(Batch.PER_CLASS)
+@EnableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER)
+public class TabArchiverTest {
+    @ClassRule
+    public static ChromeTabbedActivityTestRule sActivityTestRule =
+            new ChromeTabbedActivityTestRule();
+
+    @Rule
+    public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
+            new BlankCTATabInitialStateRule(sActivityTestRule, false);
+
+    @ClassRule public static EmbeddedTestServerRule sTestServerRule = new EmbeddedTestServerRule();
+
+    private static final String TEST_PATH = "/chrome/test/data/android/about.html";
+
+    private EmbeddedTestServer mTestServer;
+    private ArchivedTabModelOrchestrator mArchivedTabModelOrchestrator;
+    private TabArchiver mTabArchiver;
+    private TabModel mArchivedTabModel;
+    private TabModel mRegularTabModel;
+    private TabCreator mArchivedTabCreator;
+    private TabCreator mRegularTabCreator;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestServer = sTestServerRule.getServer();
+        mArchivedTabModelOrchestrator =
+                runOnUiThreadBlockingNoException(
+                        () ->
+                                ArchivedTabModelOrchestrator.getForProfile(
+                                        sActivityTestRule
+                                                .getActivity()
+                                                .getProfileProviderSupplier()
+                                                .get()
+                                                .getOriginalProfile()));
+        mArchivedTabModel = mArchivedTabModelOrchestrator.getTabModelSelector().getModel(false);
+        mRegularTabModel = sActivityTestRule.getActivity().getCurrentTabModel();
+        mArchivedTabCreator = mArchivedTabModelOrchestrator.getArchivedTabCreator();
+        mRegularTabCreator = sActivityTestRule.getActivity().getTabCreator(false);
+
+        mTabArchiver =
+                new TabArchiver(
+                        mArchivedTabCreator,
+                        mArchivedTabModel,
+                        AsyncTabParamsManagerSingleton.getInstance());
+    }
+
+    @Test
+    @MediumTest
+    public void testArchiveThenUnarchiveTab() throws Exception {
+        Tab tab =
+                sActivityTestRule.loadUrlInNewTab(
+                        mTestServer.getURL(TEST_PATH), /* incognito= */ false);
+
+        assertEquals(2, mRegularTabModel.getCount());
+        assertEquals(0, mArchivedTabModel.getCount());
+
+        runOnUiThreadBlocking(() -> mTabArchiver.archiveAndRemoveTab(mRegularTabModel, tab));
+
+        assertEquals(1, mRegularTabModel.getCount());
+        assertEquals(1, mArchivedTabModel.getCount());
+
+        runOnUiThreadBlocking(
+                () ->
+                        mTabArchiver.unarchiveAndRestoreTab(
+                                mRegularTabCreator, mArchivedTabModel.getTabAt(0)));
+
+        assertEquals(2, mRegularTabModel.getCount());
+        assertEquals(0, mArchivedTabModel.getCount());
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditorTest.java
index 5951b07..6ce2d3c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditorTest.java
@@ -6,6 +6,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.os.Bundle;
+
 import androidx.test.filters.MediumTest;
 
 import org.junit.Assert;
@@ -16,10 +18,13 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.Features;
+import org.chromium.chrome.browser.autofill.AutofillEditorBase;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
+import org.chromium.chrome.browser.autofill.PersonalDataManager.Iban;
 import org.chromium.chrome.browser.settings.SettingsActivity;
 import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.components.autofill.IbanRecordType;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 @RunWith(ChromeJUnit4ClassRunner.class)
@@ -33,6 +38,20 @@
 
     private AutofillTestHelper mAutofillTestHelper;
 
+    private Bundle fragmentArgs(String guid) {
+        Bundle args = new Bundle();
+        args.putString(AutofillEditorBase.AUTOFILL_GUID, guid);
+        return args;
+    }
+
+    private static final Iban VALID_BELGIUM_IBAN =
+            new Iban.Builder()
+                    .setGuid("")
+                    .setNickname("My IBAN")
+                    .setRecordType(IbanRecordType.UNKNOWN)
+                    .setValue("BE71096123456769")
+                    .build();
+
     @Before
     public void setUp() {
         mAutofillTestHelper = new AutofillTestHelper();
@@ -46,6 +65,30 @@
         return autofillLocalIbanEditorFragment;
     }
 
+    private void setNicknameInEditor(
+            AutofillLocalIbanEditor autofillLocalIbanEditorFragment, String nickname) {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    try {
+                        autofillLocalIbanEditorFragment.mNickname.setText(nickname);
+                    } catch (Exception e) {
+                        Assert.fail("Failed to set Nickname");
+                    }
+                });
+    }
+
+    private void setValueInEditor(
+            AutofillLocalIbanEditor autofillLocalIbanEditorFragment, String value) {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    try {
+                        autofillLocalIbanEditorFragment.mValue.setText(value);
+                    } catch (Exception e) {
+                        Assert.fail("Failed to set IBAN");
+                    }
+                });
+    }
+
     @Test
     @MediumTest
     public void testValidIbanValueEnablesSaveButton() throws Exception {
@@ -53,15 +96,8 @@
                 setUpDefaultAutofillLocalIbanEditorFragment();
 
         // Valid Russia IBAN value.
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> {
-                    try {
-                        autofillLocalIbanEditorFragment.mValue.setText(
-                                "RU0204452560040702810412345678901");
-                    } catch (Exception e) {
-                        Assert.fail("Failed to set IBAN");
-                    }
-                });
+        setValueInEditor(
+                autofillLocalIbanEditorFragment, /* value= */ "RU0204452560040702810412345678901");
 
         assertThat(autofillLocalIbanEditorFragment.mDoneButton.isEnabled()).isTrue();
     }
@@ -73,16 +109,77 @@
                 setUpDefaultAutofillLocalIbanEditorFragment();
 
         // Invalid Russia IBAN value.
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> {
-                    try {
-                        autofillLocalIbanEditorFragment.mValue.setText(
-                                "RU0204452560040702810412345678902");
-                    } catch (Exception e) {
-                        Assert.fail("Failed to set IBAN");
-                    }
-                });
+        setValueInEditor(
+                autofillLocalIbanEditorFragment, /* value= */ "RU0204452560040702810412345678902");
 
         assertThat(autofillLocalIbanEditorFragment.mDoneButton.isEnabled()).isFalse();
     }
+
+    @Test
+    @MediumTest
+    public void testEditIban_whenIbanIsNotEdited_keepsSaveButtonDisabled() throws Exception {
+        String guid = mAutofillTestHelper.addOrUpdateLocalIban(VALID_BELGIUM_IBAN);
+
+        SettingsActivity activity =
+                mSettingsActivityTestRule.startSettingsActivity(fragmentArgs(guid));
+        AutofillLocalIbanEditor autofillLocalIbanEditorFragment =
+                (AutofillLocalIbanEditor) activity.getMainFragment();
+
+        assertThat(autofillLocalIbanEditorFragment.mNickname.getText().toString())
+                .isEqualTo("My IBAN");
+        assertThat(autofillLocalIbanEditorFragment.mValue.getText().toString())
+                .isEqualTo("BE71096123456769");
+        // If neither the value nor the nickname is modified, the Done button should remain
+        // disabled.
+        assertThat(autofillLocalIbanEditorFragment.mDoneButton.isEnabled()).isFalse();
+    }
+
+    @Test
+    @MediumTest
+    public void testEditIban_whenIbanValueIsEditedFromValidToInvalid_disablesSaveButton()
+            throws Exception {
+        String guid = mAutofillTestHelper.addOrUpdateLocalIban(VALID_BELGIUM_IBAN);
+
+        SettingsActivity activity =
+                mSettingsActivityTestRule.startSettingsActivity(fragmentArgs(guid));
+        AutofillLocalIbanEditor autofillLocalIbanEditorFragment =
+                (AutofillLocalIbanEditor) activity.getMainFragment();
+
+        // Change IBAN value from valid to invalid.
+        setValueInEditor(autofillLocalIbanEditorFragment, /* value= */ "BE710961234567600");
+
+        assertThat(autofillLocalIbanEditorFragment.mDoneButton.isEnabled()).isFalse();
+    }
+
+    @Test
+    @MediumTest
+    public void testEditIban_whenIbanValueIsEditedToAnotherValidValue_enablesSaveButton()
+            throws Exception {
+        String guid = mAutofillTestHelper.addOrUpdateLocalIban(VALID_BELGIUM_IBAN);
+
+        SettingsActivity activity =
+                mSettingsActivityTestRule.startSettingsActivity(fragmentArgs(guid));
+        AutofillLocalIbanEditor autofillLocalIbanEditorFragment =
+                (AutofillLocalIbanEditor) activity.getMainFragment();
+
+        setValueInEditor(
+                autofillLocalIbanEditorFragment, /* value= */ "GB82 WEST 1234 5698 7654 32");
+
+        assertThat(autofillLocalIbanEditorFragment.mDoneButton.isEnabled()).isTrue();
+    }
+
+    @Test
+    @MediumTest
+    public void testEditIban_whenIbanNicknameIsEdited_enablesSaveButton() throws Exception {
+        String guid = mAutofillTestHelper.addOrUpdateLocalIban(VALID_BELGIUM_IBAN);
+
+        SettingsActivity activity =
+                mSettingsActivityTestRule.startSettingsActivity(fragmentArgs(guid));
+        AutofillLocalIbanEditor autofillLocalIbanEditorFragment =
+                (AutofillLocalIbanEditor) activity.getMainFragment();
+
+        setNicknameInEditor(autofillLocalIbanEditorFragment, /* nickname= */ "My doctor's IBAN");
+
+        assertThat(autofillLocalIbanEditorFragment.mDoneButton.isEnabled()).isTrue();
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
index 56cc3734..c7cd461 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
@@ -63,6 +63,7 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
+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;
@@ -849,6 +850,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/333776487")
     public void testNavigationFromXHRCallbackAndLostActivationLongTimeout() throws Exception {
         mActivityTestRule.startMainActivityOnBlankPage();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
index 5ccc4b5..a7ee589 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
@@ -9,6 +9,7 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -313,7 +314,9 @@
     public void touchNavigation_deleteMostVisitedTile() throws Exception {
         ModalDialogManager manager = mAutocomplete.getModalDialogManagerForTest();
         longClickTileAtPosition(2);
-        verify(mController, times(1)).stop(/* clear?=*/ eq(false));
+        // onTopResumedActivityChanged calls `hideSuggestions()` which may bump the number of times
+        // `stop()` is called.
+        verify(mController, atLeastOnce()).stop(/* clear?=*/ eq(false));
 
         // Wait for the delete dialog to come up...
         CriteriaHelper.pollUiThread(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
index e566b88..d0b90374 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
@@ -23,7 +23,6 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.lifecycle.Stage;
 
 import org.hamcrest.Matchers;
 import org.junit.Assert;
@@ -39,7 +38,6 @@
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Callback;
 import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
@@ -47,13 +45,10 @@
 import org.chromium.base.test.util.CriteriaNotSatisfiedException;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.DoNotBatch;
-import org.chromium.base.test.util.Features.DisableFeatures;
-import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.locale.LocaleManagerDelegate;
@@ -266,47 +261,6 @@
 
     @Test
     @SmallTest
-    @DisableFeatures(ChromeFeatureList.BACK_GESTURE_REFACTOR)
-    public void testBackPressFinishActivity() throws Exception {
-        SearchActivity searchActivity = startSearchActivity();
-
-        // Wait for the Activity to fully load.
-        mTestDelegate.shouldDelayNativeInitializationCallback.waitForCallback(0);
-        mTestDelegate.showSearchEngineDialogIfNeededCallback.waitForCallback(0);
-        mTestDelegate.onFinishDeferredInitializationCallback.waitForCallback(0);
-
-        // Type in anything.  It should force the suggestions to appear.
-        mOmnibox.requestFocus();
-        searchActivity.handleBackKeyPressed();
-
-        ApplicationTestUtils.waitForActivityState(
-                "Back press should finish the activity", searchActivity, Stage.DESTROYED);
-    }
-
-    /**
-     * Same with {@link #testBackPressFinishActivity()}, but with predictive back gesture enabled.
-     */
-    @Test
-    @SmallTest
-    @EnableFeatures(ChromeFeatureList.BACK_GESTURE_REFACTOR)
-    public void testBackPressFinishActivity_BackRefactored() throws Exception {
-        SearchActivity searchActivity = startSearchActivity();
-
-        // Wait for the Activity to fully load.
-        mTestDelegate.shouldDelayNativeInitializationCallback.waitForCallback(0);
-        mTestDelegate.showSearchEngineDialogIfNeededCallback.waitForCallback(0);
-        mTestDelegate.onFinishDeferredInitializationCallback.waitForCallback(0);
-
-        // Type in anything.  It should force the suggestions to appear.
-        mOmnibox.requestFocus();
-        searchActivity.getOnBackPressedDispatcher().onBackPressed();
-
-        ApplicationTestUtils.waitForActivityState(
-                "Back press should finish the activity", searchActivity, Stage.DESTROYED);
-    }
-
-    @Test
-    @SmallTest
     public void testStartsBrowserAfterUrlSubmitted_aboutblank() throws Exception {
         verifyUrlLoads(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
index 67b4f6e..097be84 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
@@ -35,7 +35,6 @@
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.locale.LocaleManagerDelegate;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
-import org.chromium.chrome.browser.searchwidget.SearchActivity.SearchActivityDelegate;
 import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityClient;
 import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityPreferencesManager.SearchActivityPreferences;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -51,13 +50,6 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
 public class SearchWidgetProviderTest {
-    private static class TestSearchDelegate extends SearchActivityDelegate {
-        @Override
-        public boolean isActivityDisabledForTests() {
-            return true;
-        }
-    }
-
     private static final class TestDelegate
             extends SearchWidgetProvider.SearchWidgetProviderDelegate {
         public static final int[] ALL_IDS = {11684, 20170525};
@@ -105,7 +97,6 @@
     @Before
     public void setUp() {
         ChromeApplicationTestUtils.setUp(ApplicationProvider.getApplicationContext());
-        SearchActivity.setDelegateForTests(new TestSearchDelegate());
 
         mContext = new TestContext();
         mDelegate = new TestDelegate(mContext);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SyncConsentFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SyncConsentFragmentTest.java
index 43545314..7d49cf31 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SyncConsentFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SyncConsentFragmentTest.java
@@ -1028,6 +1028,7 @@
     @LargeTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.R)
     @EnableFeatures(SigninFeatures.MINOR_MODE_RESTRICTIONS_FOR_HISTORY_SYNC_OPT_IN)
+    @DisabledTest(message = "https://crbug.com/333735758")
     public void
             testAutomotiveDevice_deviceLockCreated_syncAcceptedSuccessfully_withMinorModeRestrictionsEnabled()
                     throws IOException {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreatorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreatorTest.java
new file mode 100644
index 0000000..479634f
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreatorTest.java
@@ -0,0 +1,137 @@
+// 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.tabmodel;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import static org.chromium.base.ThreadUtils.runOnUiThreadBlocking;
+import static org.chromium.base.ThreadUtils.runOnUiThreadBlockingNoException;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.Features.EnableFeatures;
+import org.chromium.build.BuildConfig;
+import org.chromium.chrome.browser.app.tabmodel.ArchivedTabModelOrchestrator;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tab.TabState;
+import org.chromium.chrome.browser.tab.TabStateExtractor;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.EmbeddedTestServerRule;
+
+/** Tests for ChromeTabCreator. */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@Batch(Batch.PER_CLASS)
+@EnableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER)
+public class ArchivedTabCreatorTest {
+    @ClassRule
+    public static ChromeTabbedActivityTestRule sActivityTestRule =
+            new ChromeTabbedActivityTestRule();
+
+    @Rule
+    public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
+            new BlankCTATabInitialStateRule(sActivityTestRule, false);
+
+    @ClassRule public static EmbeddedTestServerRule sTestServerRule = new EmbeddedTestServerRule();
+
+    private static final String TEST_PATH = "/chrome/test/data/android/about.html";
+
+    private EmbeddedTestServer mTestServer;
+    private Profile mProfile;
+    private ArchivedTabModelOrchestrator mOrchestrator;
+    private TabCreator mTabCreator;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestServer = sTestServerRule.getServer();
+        runOnUiThreadBlocking(
+                () -> {
+                    mProfile =
+                            sActivityTestRule
+                                    .getActivity()
+                                    .getProfileProviderSupplier()
+                                    .get()
+                                    .getOriginalProfile();
+                    mOrchestrator = ArchivedTabModelOrchestrator.getForProfile(mProfile);
+                    mTabCreator = mOrchestrator.getArchivedTabCreator();
+                });
+    }
+
+    @After
+    public void tearDown() {
+        runOnUiThreadBlocking(() -> mOrchestrator.destroy());
+    }
+
+    @Test
+    @MediumTest
+    public void testCreateFrozenTab() throws Exception {
+        Tab tab =
+                sActivityTestRule.loadUrlInNewTab(
+                        mTestServer.getURL(TEST_PATH), /* incognito= */ false);
+        Tab frozenTab =
+                runOnUiThreadBlockingNoException(
+                        () -> {
+                            TabState state = TabStateExtractor.from(tab);
+                            sActivityTestRule.getActivity().getCurrentTabModel().closeTab(tab);
+                            return mTabCreator.createFrozenTab(state, tab.getId(), /* index= */ 0);
+                        });
+        assertNotNull(frozenTab);
+        assertNull(frozenTab.getWebContents());
+    }
+
+    @Test
+    @MediumTest
+    public void testRestoreFallback() {
+        runOnUiThreadBlocking(
+                () -> {
+                    assertNotNull(
+                            mTabCreator.createNewTab(
+                                    new LoadUrlParams(mTestServer.getURL(TEST_PATH)),
+                                    TabLaunchType.FROM_RESTORE,
+                                    null));
+                });
+    }
+
+    @Test
+    @MediumTest
+    public void testRestoreFallback_AssertionErrorWhenTabLaunchTypeIncorrect() throws Exception {
+        // Test is a no-op when asserts are disabled.
+        if (!BuildConfig.ENABLE_ASSERTS) return;
+
+        runOnUiThreadBlocking(
+                () -> {
+                    try {
+                        mTabCreator.createNewTab(
+                                new LoadUrlParams(mTestServer.getURL(TEST_PATH)),
+                                TabLaunchType.FROM_CHROME_UI,
+                                null);
+                    } catch (AssertionError e) {
+                        return;
+                    }
+                    fail(
+                            "Creating a non-frozen tab should fail with an assert when the"
+                                    + " launch type is incorrect.");
+                });
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityTabDelegateFactoryUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityTabDelegateFactoryUnitTest.java
new file mode 100644
index 0000000..2f234d3
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityTabDelegateFactoryUnitTest.java
@@ -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.
+
+package org.chromium.chrome.browser.searchwidget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.blink.mojom.DisplayMode;
+import org.chromium.chrome.browser.searchwidget.SearchActivityTabDelegateFactory.WebContentsDelegate;
+import org.chromium.chrome.browser.tab.Tab;
+
+/**
+ * Trivial test suite ensuring that all of the TabDelegateFactory calls have no unexpected side
+ * effects.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class SearchActivityTabDelegateFactoryUnitTest {
+    public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule();
+    private @Mock Tab mTab;
+    private @Spy SearchActivityTabDelegateFactory mFactory;
+    private @Spy WebContentsDelegate mWebContentsDelegate;
+
+    @Before
+    public void setUp() {
+        // We could rely on just the @Spy above, but we want to be sure about what Factory produces.
+        // Removing @Spy above could result in Proguard stripping some of the symbols, making the
+        // test produce confusing results.
+        mWebContentsDelegate = spy((WebContentsDelegate) mFactory.createWebContentsDelegate(mTab));
+        clearInvocations(mFactory);
+    }
+
+    @Test
+    public void tabWebContentsDelegate_getDisplayMode() {
+        assertEquals(DisplayMode.BROWSER, mWebContentsDelegate.getDisplayMode());
+        verifyNoMoreInteractions(mTab);
+    }
+
+    @Test
+    public void tabWebContentsDelegate_shouldResumeRequestsForCreatedWindow() {
+        assertFalse(mWebContentsDelegate.shouldResumeRequestsForCreatedWindow());
+        verifyNoMoreInteractions(mTab);
+    }
+
+    @Test
+    public void tabWebContentsDelegate_addNewContents() {
+        assertFalse(mWebContentsDelegate.addNewContents(null, null, 0, null, false));
+        verifyNoMoreInteractions(mTab);
+    }
+
+    @Test
+    public void tabWebContentsDelegate_setOverlayMode() {
+        mWebContentsDelegate.setOverlayMode(false);
+        // Ignore the call itself.
+        verify(mWebContentsDelegate).setOverlayMode(false);
+        verifyNoMoreInteractions(mFactory, mWebContentsDelegate, mTab);
+    }
+
+    @Test
+    public void tabWebContentsDelegate_canShowAppBanners() {
+        assertFalse(mWebContentsDelegate.canShowAppBanners());
+        verifyNoMoreInteractions(mTab);
+    }
+
+    @Test
+    public void createExternalNavigationHandler() {
+        assertNull(mFactory.createExternalNavigationHandler(mTab));
+        verifyNoMoreInteractions(mTab);
+    }
+
+    @Test
+    public void createContextMenuPopulatorFactory() {
+        assertNull(mFactory.createContextMenuPopulatorFactory(mTab));
+        verifyNoMoreInteractions(mTab);
+    }
+
+    @Test
+    public void createBrowserControlsVisibilityDelegate() {
+        assertNull(mFactory.createBrowserControlsVisibilityDelegate(mTab));
+        verifyNoMoreInteractions(mTab);
+    }
+
+    @Test
+    public void createNativePage() {
+        // Let the test crash if any parameters are actually used.
+        assertNull(mFactory.createNativePage(null, null, mTab, null));
+        verifyNoMoreInteractions(mTab);
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityUnitTest.java
index 32aa653..7af93d2 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityUnitTest.java
@@ -585,4 +585,39 @@
 
         verify(mActivity, never()).finishDeferredInitialization();
     }
+
+    @Test
+    public void cancelSearch_onBackKeyPressed() {
+        mActivity.handleNewIntent(new Intent());
+
+        assertFalse(mActivity.isFinishing());
+        assertFalse(mActivity.isActivityFinishingOrDestroyed());
+        mActivity.handleBackKeyPressed();
+        assertTrue(mActivity.isActivityFinishingOrDestroyed());
+        assertTrue(mActivity.isFinishing());
+    }
+
+    @Test
+    public void cancelSearch_onBackGesture() {
+        // Same as above, but with predictive back gesture enabled.
+        mActivity.handleNewIntent(new Intent());
+
+        assertFalse(mActivity.isFinishing());
+        assertFalse(mActivity.isActivityFinishingOrDestroyed());
+        mActivity.getOnBackPressedDispatcher().onBackPressed();
+        assertTrue(mActivity.isActivityFinishingOrDestroyed());
+        assertTrue(mActivity.isFinishing());
+    }
+
+    @Test
+    public void cancelSearch_onTapOutside() {
+        mActivity.handleNewIntent(new Intent());
+
+        assertFalse(mActivity.isFinishing());
+        assertFalse(mActivity.isActivityFinishingOrDestroyed());
+        var view = mActivity.createContentView();
+        view.performClick();
+        assertTrue(mActivity.isActivityFinishingOrDestroyed());
+        assertTrue(mActivity.isFinishing());
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tab/TabArchiveSettingsTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tab/TabArchiveSettingsTest.java
new file mode 100644
index 0000000..4e99235c
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tab/TabArchiveSettingsTest.java
@@ -0,0 +1,56 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.tab;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import static org.chromium.chrome.browser.tab.TabArchiveSettings.ARCHIVE_ENABLED_DEFAULT;
+import static org.chromium.chrome.browser.tab.TabArchiveSettings.ARCHIVE_TIME_DELTA_HOURS_DEFAULT;
+import static org.chromium.chrome.browser.tab.TabArchiveSettings.AUTO_DELETE_ENABLED_DEFAULT;
+import static org.chromium.chrome.browser.tab.TabArchiveSettings.AUTO_DELETE_TIME_DELTA_HOURS_DEFAULT;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.shared_preferences.SharedPreferencesManager;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
+
+/** Tests for {@link TabArchiveSettings}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class TabArchiveSettingsTest {
+
+    private SharedPreferencesManager mSharedPrefs;
+
+    @Before
+    public void setUp() {
+        mSharedPrefs = ChromeSharedPreferences.getInstance();
+    }
+
+    @Test
+    public void testSettings() {
+        TabArchiveSettings settings = new TabArchiveSettings(mSharedPrefs);
+        assertEquals(ARCHIVE_ENABLED_DEFAULT, settings.getArchiveEnabled());
+        assertEquals(ARCHIVE_TIME_DELTA_HOURS_DEFAULT, settings.getArchiveTimeDeltaHours());
+        assertEquals(AUTO_DELETE_ENABLED_DEFAULT, settings.isAutoDeleteEnabled());
+        assertEquals(AUTO_DELETE_TIME_DELTA_HOURS_DEFAULT, settings.getAutoDeleteTimeDeltaHours());
+
+        settings.setArchiveEnabled(false);
+        assertFalse(settings.getArchiveEnabled());
+
+        settings.setArchiveTimeDeltaHours(1);
+        assertEquals(1, settings.getArchiveTimeDeltaHours());
+
+        settings.setAutoDeleteEnabled(false);
+        assertFalse(settings.isAutoDeleteEnabled());
+
+        settings.setAutoDeleteTimeDeltaHours(1);
+        assertEquals(1, settings.getArchiveTimeDeltaHours());
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tabmodel/ArchivedTabModelSelectorImplTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tabmodel/ArchivedTabModelSelectorImplTest.java
new file mode 100644
index 0000000..547da67c
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tabmodel/ArchivedTabModelSelectorImplTest.java
@@ -0,0 +1,249 @@
+// 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.tabmodel;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+import org.chromium.base.Callback;
+import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.flags.ActivityType;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.profiles.ProfileProvider;
+import org.chromium.chrome.browser.tab.MockTab;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabCreationState;
+import org.chromium.chrome.browser.tab.TabDelegateFactory;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tab.TabSelectionType;
+import org.chromium.chrome.browser.tab_ui.TabContentManager;
+import org.chromium.chrome.browser.tabmodel.NextTabPolicy.NextTabPolicySupplier;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
+import org.chromium.chrome.test.util.browser.tabmodel.MockTabCreatorManager;
+import org.chromium.chrome.test.util.browser.tabmodel.MockTabModel;
+import org.chromium.ui.base.WindowAndroid;
+
+import java.lang.ref.WeakReference;
+
+/** Unit tests for {@link TabModelSelectorImpl}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class ArchivedTabModelSelectorImplTest {
+    // Test activity type that does not restore tab on cold restart.
+    // Any type other than ActivityType.TABBED works.
+    private static final @ActivityType int NO_RESTORE_TYPE = ActivityType.CUSTOM_TAB;
+
+    @Mock private TabContentManager mMockTabContentManager;
+    @Mock private TabDelegateFactory mTabDelegateFactory;
+    @Mock private NextTabPolicySupplier mNextTabPolicySupplier;
+
+    @Mock
+    private IncognitoTabModelObserver.IncognitoReauthDialogDelegate
+            mIncognitoReauthDialogDelegateMock;
+
+    @Mock private Callback<TabModel> mTabModelSupplierObserverMock;
+    @Mock private Callback<Tab> mTabSupplierObserverMock;
+    @Mock private Callback<Integer> mTabCountSupplierObserverMock;
+    @Mock private TabModelSelectorObserver mTabModelSelectorObserverMock;
+    @Mock private ProfileProvider mProfileProvider;
+    @Mock private Profile mProfile;
+    @Mock private Profile mIncognitoProfile;
+    @Mock private Context mContext;
+
+    private ArchivedTabModelSelectorImpl mTabModelSelector;
+    private MockTabCreatorManager mTabCreatorManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(true).when(mIncognitoProfile).isOffTheRecord();
+        mTabCreatorManager = new MockTabCreatorManager();
+
+        AsyncTabParamsManager realAsyncTabParamsManager =
+                AsyncTabParamsManagerFactory.createAsyncTabParamsManager();
+        mTabModelSelector =
+                new ArchivedTabModelSelectorImpl(
+                        mProfile,
+                        mTabCreatorManager,
+                        (tabModel) -> new TabGroupModelFilter(tabModel),
+                        mNextTabPolicySupplier,
+                        realAsyncTabParamsManager);
+        assertTrue(currentTabModelSupplierHasObservers());
+        assertNull(mTabModelSelector.getCurrentTabModelSupplier().get());
+        assertNull(mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter());
+
+        mTabCreatorManager.initialize(mTabModelSelector);
+        mTabModelSelector.onNativeLibraryReadyInternal(
+                mMockTabContentManager,
+                new MockTabModel(mProfile, null),
+                new MockTabModel(mIncognitoProfile, null));
+
+        assertEquals(
+                mTabModelSelector.getModel(/* isIncognito= */ false),
+                mTabModelSelector.getCurrentTabModelSupplier().get());
+        assertEquals(
+                mTabModelSelector.getCurrentModel(),
+                mTabModelSelector.getCurrentTabModelSupplier().get());
+        assertEquals(
+                mTabModelSelector.getCurrentModel(),
+                mTabModelSelector
+                        .getTabModelFilterProvider()
+                        .getCurrentTabModelFilter()
+                        .getTabModel());
+    }
+
+    @After
+    public void tearDown() {
+        mTabModelSelector.destroy();
+        assertFalse(currentTabModelSupplierHasObservers());
+    }
+
+    @Test
+    public void testCurrentTabSupplier() {
+        mTabModelSelector.getCurrentTabSupplier().addObserver(mTabSupplierObserverMock);
+        assertNull(mTabModelSelector.getCurrentTabSupplier().get());
+
+        MockTab normalTab = new MockTab(1, mProfile);
+        mTabModelSelector
+                .getModel(false)
+                .addTab(
+                        normalTab,
+                        0,
+                        TabLaunchType.FROM_CHROME_UI,
+                        TabCreationState.LIVE_IN_FOREGROUND);
+        mTabModelSelector
+                .getModel(false)
+                .setIndex(0, TabSelectionType.FROM_USER, /* skipLoadingTab= */ true);
+        assertEquals(normalTab, mTabModelSelector.getModel(false).getCurrentTabSupplier().get());
+        assertEquals(normalTab, mTabModelSelector.getCurrentTabSupplier().get());
+        assertEquals(
+                mTabModelSelector.getModel(false),
+                mTabModelSelector
+                        .getTabModelFilterProvider()
+                        .getCurrentTabModelFilter()
+                        .getTabModel());
+        ShadowLooper.runUiThreadTasks();
+        verify(mTabSupplierObserverMock).onResult(eq(normalTab));
+        mTabModelSelector.getCurrentTabSupplier().removeObserver(mTabSupplierObserverMock);
+    }
+
+    @Test
+    public void testCurrentModelTabCountSupplier() {
+        mTabModelSelector
+                .getCurrentModelTabCountSupplier()
+                .addObserver(mTabCountSupplierObserverMock);
+        assertEquals(0, mTabModelSelector.getCurrentModelTabCountSupplier().get().intValue());
+        ShadowLooper.runUiThreadTasks();
+        verify(mTabCountSupplierObserverMock).onResult(0);
+
+        MockTab normalTab1 = new MockTab(1, mProfile);
+        mTabModelSelector
+                .getModel(false)
+                .addTab(
+                        normalTab1,
+                        0,
+                        TabLaunchType.FROM_CHROME_UI,
+                        TabCreationState.LIVE_IN_FOREGROUND);
+        ShadowLooper.runUiThreadTasks();
+        verify(mTabCountSupplierObserverMock).onResult(1);
+        assertEquals(1, mTabModelSelector.getCurrentModelTabCountSupplier().get().intValue());
+
+        MockTab normalTab2 = new MockTab(2, mProfile);
+        mTabModelSelector
+                .getModel(false)
+                .addTab(
+                        normalTab2,
+                        0,
+                        TabLaunchType.FROM_CHROME_UI,
+                        TabCreationState.LIVE_IN_FOREGROUND);
+        ShadowLooper.runUiThreadTasks();
+        verify(mTabCountSupplierObserverMock).onResult(2);
+        assertEquals(2, mTabModelSelector.getCurrentModelTabCountSupplier().get().intValue());
+
+        mTabModelSelector.getModel(false).removeTab(normalTab1);
+        mTabModelSelector.getModel(false).removeTab(normalTab2);
+        ShadowLooper.runUiThreadTasks();
+        assertEquals(0, mTabModelSelector.getCurrentModelTabCountSupplier().get().intValue());
+        verify(mTabCountSupplierObserverMock, times(2)).onResult(0);
+
+        mTabModelSelector
+                .getCurrentModelTabCountSupplier()
+                .removeObserver(mTabCountSupplierObserverMock);
+    }
+
+    @Test
+    public void testTabActivityAttachmentChanged_detaching() {
+        MockTab tab = new MockTab(1, mProfile);
+        mTabModelSelector
+                .getModel(false)
+                .addTab(tab, 0, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
+        tab.updateAttachment(null, null);
+
+        Assert.assertEquals(
+                "detaching a tab should result in it being removed from the model",
+                0,
+                mTabModelSelector.getModel(false).getCount());
+    }
+
+    @Test
+    public void testTabActivityAttachmentChanged_movingWindows() {
+        MockTab tab = new MockTab(1, mProfile);
+        mTabModelSelector
+                .getModel(false)
+                .addTab(tab, 0, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
+        WindowAndroid window = mock(WindowAndroid.class);
+        WeakReference<Context> weakContext = new WeakReference<>(mContext);
+        when(window.getContext()).thenReturn(weakContext);
+        tab.updateAttachment(window, mTabDelegateFactory);
+
+        Assert.assertEquals(
+                "moving a tab between windows shouldn't remove it from the model",
+                1,
+                mTabModelSelector.getModel(false).getCount());
+    }
+
+    @Test
+    public void testTabActivityAttachmentChanged_detachingWhileReparentingInProgress() {
+        MockTab tab = new MockTab(1, mProfile);
+        mTabModelSelector
+                .getModel(false)
+                .addTab(tab, 0, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
+
+        mTabModelSelector.enterReparentingMode();
+        tab.updateAttachment(null, null);
+
+        Assert.assertEquals(
+                "tab shouldn't be removed while reparenting is in progress",
+                1,
+                mTabModelSelector.getModel(false).getCount());
+    }
+
+    private boolean currentTabModelSupplierHasObservers() {
+        return ((ObservableSupplierImpl<?>) mTabModelSelector.getCurrentTabModelSupplier())
+                .hasObservers();
+    }
+}
diff --git a/chrome/app/app-Info.plist b/chrome/app/app-Info.plist
index 5654e5c9..0ce6d4d 100644
--- a/chrome/app/app-Info.plist
+++ b/chrome/app/app-Info.plist
@@ -243,6 +243,8 @@
 	<string>${CHROMIUM_MIN_SYSTEM_VERSION}</string>
 	<key>LSRequiresNativeExecution</key>
 	<true/>
+	<key>NSCameraReactionEffectGesturesEnabledDefault</key>
+	<false/>
 	<key>NSPrincipalClass</key>
 	<string>BrowserCrApplication</string>
 	<key>NSSupportsAutomaticGraphicsSwitching</key>
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index f7f7e8d..c397708 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -7470,8 +7470,14 @@
   <message name="IDS_APP_INSTALL_DIALOG_APP_INSTALLED_TITLE" desc="Title shown in the app install dialog after the app has finished installing.">
     App installed
   </message>
+  <message name="IDS_APP_INSTALL_DIALOG_APP_ALREADY_INSTALLED_TITLE" desc="Title shown in the app install dialog after the app has finished installing.">
+    App is already installed
+  </message>
   <message name="IDS_APP_INSTALL_DIALOG_NO_APP_DATA_TITLE" desc="Title shown in the app install dialog when there is no app data.">
-    Could not download app data
+    Can't install this app
+  </message>
+  <message name="IDS_APP_INSTALL_DIALOG_NO_APP_DATA_DESCRIPTION" desc="Description shown in the app install dialog when there is no app data.">
+    Couldn't download app data. Try Again.
   </message>
   <message name="IDS_APP_INSTALL_DIALOG_TRY_AGAIN_BUTTON_LABEL" desc="Button label in the app install dialog that opens the app that was just installed.">
     Try again
diff --git a/chrome/app/chromeos_strings_grdp/IDS_APP_INSTALL_DIALOG_APP_ALREADY_INSTALLED_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_APP_INSTALL_DIALOG_APP_ALREADY_INSTALLED_TITLE.png.sha1
new file mode 100644
index 0000000..51f73c5
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_APP_INSTALL_DIALOG_APP_ALREADY_INSTALLED_TITLE.png.sha1
@@ -0,0 +1 @@
+4fff1d506695aa85a4da50326370a4493c02454f
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_APP_INSTALL_DIALOG_NO_APP_DATA_DESCRIPTION.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_APP_INSTALL_DIALOG_NO_APP_DATA_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..5beb1a55
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_APP_INSTALL_DIALOG_NO_APP_DATA_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+869dd98690bced7593ecbb19a461ad760e114d89
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_APP_INSTALL_DIALOG_NO_APP_DATA_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_APP_INSTALL_DIALOG_NO_APP_DATA_TITLE.png.sha1
index 263ce82..fcc9168 100644
--- a/chrome/app/chromeos_strings_grdp/IDS_APP_INSTALL_DIALOG_NO_APP_DATA_TITLE.png.sha1
+++ b/chrome/app/chromeos_strings_grdp/IDS_APP_INSTALL_DIALOG_NO_APP_DATA_TITLE.png.sha1
@@ -1 +1 @@
-ce42cdc7e8c8fcb467acdcb93c9e2958f4f8fd8b
\ No newline at end of file
+be292969c3459716bd833ab51c5113fe5d82e2fd
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 5105dc2..3bffe6b 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -8329,6 +8329,9 @@
         <ph name="BREAK">&lt;br&gt;</ph>
         You can manage settings from the card menu or see more options in Customize Chrome.
       </message>
+      <message name="IDS_NTP_MODULES_GOOGLE_CALENDAR_TITLE" desc="Title of the Google Calendar module shown in various UIs for Customize Chrome and the NTP." meaning="Title of feature for showing a glimpse of a user's Google Calendar.">
+        Google Calendar
+      </message>
       <message name="IDS_NTP_MODULES_PHOTOS_TITLE" desc="Title shown in the header of the photos module.">
         From your Google Photos
       </message>
@@ -16359,13 +16362,6 @@
     <message name = "IDS_CHROMELABS_ENABLED_WITH_VARIATION_NAME" desc="Label for combobox option for an enabled variation that will have a description describing what variation is enabled in the placeholder.">
       Enabled – <ph name="VARIATION_NAME">$1<ex>tabs shrink to pinned tab width</ex></ph>
     </message>
-    <!-- ChromeLabs Tab Groups Save-->
-    <message name="IDS_TAB_GROUPS_SAVE_EXPERIMENT_NAME" desc="The name for the Tab Groups Save experiment.">
-      Tab Groups Save and Sync
-    </message>
-    <message name="IDS_TAB_GROUPS_SAVE_DESCRIPTION" desc="The description for the Tab Groups Save experiment.">
-      Enables saving and recalling of tab groups. Right click a tab group to save it. Recall groups from the bookmarks bar.
-    </message>
     <!-- ChromeLabs Customize Chrome SidePanel-->
     <message name="IDS_CUSTOMIZE_CHROME_SIDE_PANEL_EXPERIMENT_NAME" desc="The name for the Customize Chrome Side Panel experiment.">
       Customize Chrome Side Panel
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_GOOGLE_CALENDAR_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_GOOGLE_CALENDAR_TITLE.png.sha1
new file mode 100644
index 0000000..b624d69
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_GOOGLE_CALENDAR_TITLE.png.sha1
@@ -0,0 +1 @@
+5423e5cb403f62b60b3972099f757238820902c2
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_GROUPS_SAVE_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_GROUPS_SAVE_DESCRIPTION.png.sha1
deleted file mode 100644
index ee52e87..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_GROUPS_SAVE_DESCRIPTION.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ff77696c715d9d2d89659d5a386affbed8797920
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_GROUPS_SAVE_EXPERIMENT_NAME.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_GROUPS_SAVE_EXPERIMENT_NAME.png.sha1
deleted file mode 100644
index ee52e87..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_GROUPS_SAVE_EXPERIMENT_NAME.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ff77696c715d9d2d89659d5a386affbed8797920
\ No newline at end of file
diff --git a/chrome/app/helper-Info.plist b/chrome/app/helper-Info.plist
index 6f8600e3d..a8c71a54 100644
--- a/chrome/app/helper-Info.plist
+++ b/chrome/app/helper-Info.plist
@@ -29,6 +29,8 @@
 	<string>${CHROMIUM_MIN_SYSTEM_VERSION}</string>
 	<key>LSUIElement</key>
 	<string>1</string>
+	<key>NSCameraReactionEffectGesturesEnabledDefault</key>
+	<false/>
 	<key>NSSupportsAutomaticGraphicsSwitching</key>
 	<true/>
 </dict>
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 6668792..699f538 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -971,6 +971,9 @@
   <message name="IDS_OS_SETTINGS_PERSONALIZATION_MENU_ITEM_DESCRIPTION" desc="Description for the Personalization menu item in the left menu.">
     Dark theme, screen saver
   </message>
+  <message name="IDS_OS_SETTINGS_PERSONALIZATION_MENU_ITEM_DESCRIPTION_GUEST_MODE" desc="Description for the Personalization menu item in the left menu, for guest mode.">
+    Dark theme
+  </message>
   <message name="IDS_OS_SETTINGS_OPEN_PERSONALIZATION_HUB" desc="Title for the link to open personalization hub.">
     Set your wallpaper &amp; style
   </message>
@@ -980,6 +983,9 @@
   <message name="IDS_OS_SETTINGS_REVAMP_OPEN_PERSONALIZATION_HUB_SUBTITLE" desc="Description for the link to open personalization hub.">
     Personalize wallpaper, screen saver, dark theme, and more
   </message>
+  <message name="IDS_OS_SETTINGS_REVAMP_OPEN_PERSONALIZATION_HUB_SUBTITLE_GUEST_MODE" desc="Description for the link to open personalization hub in guest mode.">
+    Personalize wallpaper, dark theme, and more
+  </message>
 
   <!-- Search and Assistant section. -->
   <message name="IDS_OS_SETTINGS_SEARCH_ENGINE_LABEL" desc="Label in OS settings describing search engine behavior.">
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PERSONALIZATION_MENU_ITEM_DESCRIPTION_GUEST_MODE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PERSONALIZATION_MENU_ITEM_DESCRIPTION_GUEST_MODE.png.sha1
new file mode 100644
index 0000000..cca2e99b
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PERSONALIZATION_MENU_ITEM_DESCRIPTION_GUEST_MODE.png.sha1
@@ -0,0 +1 @@
+9fb49788aba8353dbd8b02573d2189016c0eb055
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_OPEN_PERSONALIZATION_HUB_SUBTITLE_GUEST_MODE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_OPEN_PERSONALIZATION_HUB_SUBTITLE_GUEST_MODE.png.sha1
new file mode 100644
index 0000000..52c6da1
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_OPEN_PERSONALIZATION_HUB_SUBTITLE_GUEST_MODE.png.sha1
@@ -0,0 +1 @@
+d5d820b96134f1eecf6297f7ce2364d567cdc2ba
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index c63fbd7..9a63409 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -2718,16 +2718,13 @@
     Not allowed to use fonts installed on your device
   </message>
   <message name="IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_DESCRIPTION" desc="Label explaining the automatic fullscreen content setting.">
-    Sites use this feature to offer immersive fullscreen experiences regardless of user gestures
-  </message>
-  <message name="IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_BLOCK" desc="Label explaining that automatic fullscreen is blocked by default, meaning a user gesture is required.">
-    Entering fullscreen requires a user gesture by default
+    Sites use this feature to enter full screen automatically. Typically, entering full screen requires user interaction.
   </message>
   <message name="IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_ALLOWED_EXCEPTIONS" desc="Label for the allowed exceptions site list of the automatic fullscreen content setting.">
-    Allowed to enter fullscreen automatically
+    Allowed to enter full screen automatically
   </message>
   <message name="IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_BLOCKED_EXCEPTIONS" desc="Label for the blocked exceptions site list of the automatic fullscreen content setting.">
-    Not allowed to enter fullscreen automatically
+    Not allowed to enter full screen automatically
   </message>
   <message name="IDS_SETTINGS_SITE_SETTINGS_HID_DEVICES_DESCRIPTION" desc="Description of the hid devices site setting.">
     Sites usually connect to HID devices for features that use uncommon keyboards, game controllers, and other devices
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_ALLOWED_EXCEPTIONS.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_ALLOWED_EXCEPTIONS.png.sha1
index ffa6d039..e096923 100644
--- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_ALLOWED_EXCEPTIONS.png.sha1
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_ALLOWED_EXCEPTIONS.png.sha1
@@ -1 +1 @@
-ff6761ef77661291a9c826cc0c0fc9ae6a17a671
\ No newline at end of file
+d2da636b3b998f70da8fba61304fe8c3070b976f
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_BLOCK.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_BLOCK.png.sha1
deleted file mode 100644
index ffa6d039..0000000
--- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_BLOCK.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ff6761ef77661291a9c826cc0c0fc9ae6a17a671
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_BLOCKED_EXCEPTIONS.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_BLOCKED_EXCEPTIONS.png.sha1
index ffa6d039..e096923 100644
--- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_BLOCKED_EXCEPTIONS.png.sha1
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_BLOCKED_EXCEPTIONS.png.sha1
@@ -1 +1 @@
-ff6761ef77661291a9c826cc0c0fc9ae6a17a671
\ No newline at end of file
+d2da636b3b998f70da8fba61304fe8c3070b976f
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_DESCRIPTION.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_DESCRIPTION.png.sha1
index a5bb305e..e096923 100644
--- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_DESCRIPTION.png.sha1
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_DESCRIPTION.png.sha1
@@ -1 +1 @@
-8993905064f2584704ef3ad376e9b620b4801a03
\ No newline at end of file
+d2da636b3b998f70da8fba61304fe8c3070b976f
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index fde5042ab..5480025 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4612,6 +4612,8 @@
       "webauthn/enclave_manager.h",
       "webauthn/enclave_manager_factory.cc",
       "webauthn/enclave_manager_factory.h",
+      "webauthn/gpm_enclave_controller.cc",
+      "webauthn/gpm_enclave_controller.h",
       "webauthn/local_credential_management.cc",
       "webauthn/local_credential_management.h",
       "webauthn/observable_authenticator_list.cc",
@@ -5845,6 +5847,8 @@
       "certificate_provider/sign_requests.h",
       "certificate_provider/thread_safe_certificate_map.cc",
       "certificate_provider/thread_safe_certificate_map.h",
+      "chromeos/echo/echo_util.cc",
+      "chromeos/echo/echo_util.h",
       "chromeos/kcer/kcer_factory.cc",
       "chromeos/kcer/kcer_factory.h",
       "device_notifications/device_pinned_notification_renderer.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 796a32b3..02b46fe 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -6482,13 +6482,6 @@
      flag_descriptions::kEnableRemoveStalePolicyPinnedAppsFromShelfDescription,
      kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kRemoveStalePolicyPinnedAppsFromShelf)},
-    {"handwriting-legacy-recognition",
-     flag_descriptions::kHandwritingLegacyRecognitionName,
-     flag_descriptions::kHandwritingLegacyRecognitionDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kHandwritingLegacyRecognition)},
-    {"handwriting-library-dlc", flag_descriptions::kHandwritingLibraryDlcName,
-     flag_descriptions::kHandwritingLibraryDlcDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kHandwritingLibraryDlc)},
     {"language-packs-in-settings",
      flag_descriptions::kLanguagePacksInSettingsName,
      flag_descriptions::kLanguagePacksInSettingsDescription, kOsCrOS,
@@ -6557,15 +6550,6 @@
      FEATURE_VALUE_TYPE(features::kQuickSettingsPWANotifications)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-    {flag_descriptions::kTabGroupsSaveId, flag_descriptions::kTabGroupsSaveName,
-     flag_descriptions::kTabGroupsSaveDescription, kOsDesktop,
-     FEATURE_VALUE_TYPE(features::kTabGroupsSave)},
-
-    {flag_descriptions::kTabGroupsSaveV2Id,
-     flag_descriptions::kTabGroupsSaveV2Name,
-     flag_descriptions::kTabGroupsSaveV2Description, kOsDesktop,
-     FEATURE_VALUE_TYPE(tab_groups::kTabGroupsSaveV2)},
-
     {flag_descriptions::kScrollableTabStripFlagId,
      flag_descriptions::kScrollableTabStripName,
      flag_descriptions::kScrollableTabStripDescription, kOsDesktop,
@@ -8841,6 +8825,12 @@
      kOsCrOS | kOsLacros,
      FEATURE_VALUE_TYPE(blink::features::kFileSystemAccessGetCloudIdentifiers)},
 
+    {"gate-nv12-gmb-video-frames-on-hw-support",
+     flag_descriptions::kGateNV12GMBVideoFramesOnHWSupportName,
+     flag_descriptions::kGateNV12GMBVideoFramesOnHWSupportDescription,
+     kOsCrOS | kOsLacros,
+     FEATURE_VALUE_TYPE(features::kGateNV12GMBVideoFramesOnHWSupport)},
+
     {"lacros-color-management", flag_descriptions::kLacrosColorManagementName,
      flag_descriptions::kLacrosColorManagementDescription, kOsLacros,
      FEATURE_VALUE_TYPE(features::kLacrosColorManagement)},
@@ -10179,6 +10169,13 @@
      FEATURE_VALUE_TYPE(
          network::features::kCompressionDictionaryTransportOverHttp1)},
 
+    {"enable-compression-dictionary-transport-allow-http2",
+     flag_descriptions::kCompressionDictionaryTransportOverHttp2Name,
+     flag_descriptions::kCompressionDictionaryTransportOverHttp2Description,
+     kOsAll,
+     FEATURE_VALUE_TYPE(
+         network::features::kCompressionDictionaryTransportOverHttp2)},
+
     {"enable-compression-dictionary-transport-require-known-root-cert",
      flag_descriptions::kCompressionDictionaryTransportRequireKnownRootCertName,
      flag_descriptions::
@@ -10654,14 +10651,6 @@
      PLATFORM_FEATURE_NAME_TYPE("CrOSLateBootCrOSSOUL")},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-#if BUILDFLAG(IS_ANDROID)
-    {"grid-tab-switcher-android-animations",
-     flag_descriptions::kGridTabSwitcherAndroidAnimationsName,
-     flag_descriptions::kGridTabSwitcherAndroidAnimationsDescription,
-     kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kGridTabSwitcherAndroidAnimations)},
-#endif  // !BUILDFLAG(IS_ANDROID)
-
 #if BUILDFLAG(ENABLE_LIBRARY_CDMS)
     {"cdm-storage-database", flag_descriptions::kCdmStorageDatabaseName,
      flag_descriptions::kCdmStorageDatabaseDescription, kOsDesktop,
diff --git a/chrome/browser/accessibility/embedded_a11y_extension_loader.cc b/chrome/browser/accessibility/embedded_a11y_extension_loader.cc
index 35e09843..7ce3f21 100644
--- a/chrome/browser/accessibility/embedded_a11y_extension_loader.cc
+++ b/chrome/browser/accessibility/embedded_a11y_extension_loader.cc
@@ -110,23 +110,6 @@
   initialized_ = true;
 }
 
-void EmbeddedA11yExtensionLoader::InstallA11yHelperExtensionForReadingMode() {
-  // TODO(crbug.com/324143642): Install a11y helper extension for all platforms.
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  InstallExtensionWithId(extension_misc::kEmbeddedA11yHelperExtensionId,
-                         extension_misc::kEmbeddedA11yHelperExtensionPath,
-                         extension_misc::kEmbeddedA11yHelperManifestFilename,
-                         /*should_localize=*/true);
-#endif
-}
-
-void EmbeddedA11yExtensionLoader::RemoveA11yHelperExtensionForReadingMode() {
-  // TODO(crbug.com/324143642): Remove a11y helper extension for all platforms.
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  RemoveExtensionWithId(extension_misc::kEmbeddedA11yHelperExtensionId);
-#endif
-}
-
 void EmbeddedA11yExtensionLoader::InstallExtensionWithId(
     const std::string& extension_id,
     const std::string& extension_path,
@@ -279,3 +262,8 @@
     extension_installation_changed_callback_for_test_.Run();
   }
 }
+
+bool EmbeddedA11yExtensionLoader::IsExtensionInstalled(
+    const std::string& extension_id) {
+  return extension_map_.contains(extension_id);
+}
diff --git a/chrome/browser/accessibility/embedded_a11y_extension_loader.h b/chrome/browser/accessibility/embedded_a11y_extension_loader.h
index 7feb344..b6ffbdd 100644
--- a/chrome/browser/accessibility/embedded_a11y_extension_loader.h
+++ b/chrome/browser/accessibility/embedded_a11y_extension_loader.h
@@ -68,12 +68,6 @@
   // Should be called when the browser starts up.
   void Init();
 
-  // TODO(crbug.com/324143642): Observe the reading mode enabled/disabled state
-  // in this class instead of informing EmbeddedA11yManagerLacros to
-  // enable/disable reading mode.
-  virtual void InstallA11yHelperExtensionForReadingMode();
-  virtual void RemoveA11yHelperExtensionForReadingMode();
-
   // Install an extension.
   // `manifest_name` must live for the duration of the program. (e.g. be
   // statically allocated)
@@ -87,6 +81,9 @@
   // background page, and these extensions do not have background pages.
   void AddExtensionChangedCallbackForTest(base::RepeatingClosure callback);
 
+  // Check whether an extension is installed or not.
+  bool IsExtensionInstalled(const std::string& extension_id);
+
  private:
   // ProfileObserver:
   void OnProfileWillBeDestroyed(Profile* profile) override;
diff --git a/chrome/browser/accessibility/embedded_a11y_extension_loader_browsertest.cc b/chrome/browser/accessibility/embedded_a11y_extension_loader_browsertest.cc
index fd64aa38..da03580 100644
--- a/chrome/browser/accessibility/embedded_a11y_extension_loader_browsertest.cc
+++ b/chrome/browser/accessibility/embedded_a11y_extension_loader_browsertest.cc
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/accessibility/embedded_a11y_extension_loader.h"
+
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/component_loader.h"
 #include "chrome/browser/extensions/extension_service.h"
@@ -99,54 +100,35 @@
   std::unique_ptr<base::RunLoop> waiter_;
 };
 
-// TODO(b/324143642): test with non-Lacros extensions. Currently, the following
-// tests are tested with a Lacros extension embedded_a11y_helper_extension.
-// These tests should be tested with a cross-platform extension once the
-// extension is setup successfully.
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
 IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
-                       InstallsAndRemovesExtensionForReadingMode) {
-  ProfileManager* profile_manager = g_browser_process->profile_manager();
-  const auto& profiles = profile_manager->GetLoadedProfiles();
-  ASSERT_GT(profiles.size(), 0u);
-  Profile* profile = profiles[0];
-
-  auto* embedded_a11y_extension_loader =
-      EmbeddedA11yExtensionLoader::GetInstance();
-  embedded_a11y_extension_loader->InstallA11yHelperExtensionForReadingMode();
-  WaitForExtensionLoaded(profile,
-                         extension_misc::kEmbeddedA11yHelperExtensionId);
-
-  embedded_a11y_extension_loader->RemoveA11yHelperExtensionForReadingMode();
-  WaitForExtensionUnloaded(profile,
-                           extension_misc::kEmbeddedA11yHelperExtensionId);
-}
-
-IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
-                       InstallsRemovesAndReinstallsExtension) {
+                       // TODO(crbug.com/333813413): Re-enable this test
+                       DISABLED_InstallsRemovesAndReinstallsExtension) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   const auto& profiles = profile_manager->GetLoadedProfiles();
   ASSERT_GT(profiles.size(), 0u);
   Profile* profile = profiles[0];
 
   InstallAndWaitForExtensionLoaded(
-      profile, extension_misc::kEmbeddedA11yHelperExtensionId,
-      extension_misc::kEmbeddedA11yHelperExtensionPath,
-      extension_misc::kEmbeddedA11yHelperManifestFilename,
-      /*should_localize=*/true);
+      profile, extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
   RemoveAndWaitForExtensionUnloaded(
-      profile, extension_misc::kEmbeddedA11yHelperExtensionId);
+      profile, extension_misc::kReadingModeGDocsHelperExtensionId);
   InstallAndWaitForExtensionLoaded(
-      profile, extension_misc::kEmbeddedA11yHelperExtensionId,
-      extension_misc::kEmbeddedA11yHelperExtensionPath,
-      extension_misc::kEmbeddedA11yHelperManifestFilename,
-      /*should_localize=*/true);
+      profile, extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
   RemoveAndWaitForExtensionUnloaded(
-      profile, extension_misc::kEmbeddedA11yHelperExtensionId);
+      profile, extension_misc::kReadingModeGDocsHelperExtensionId);
 }
 
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
-                       InstallsOnMultipleProfiles) {
+                       // TODO(crbug.com/333813413): Re-enable this test
+                       DISABLED_InstallsOnMultipleProfiles) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   size_t num_extra_profiles = 2;
   for (size_t i = 0; i < num_extra_profiles; i++) {
@@ -166,47 +148,78 @@
   // Install extension for Reading Mode.
   auto* embedded_a11y_extension_loader =
       EmbeddedA11yExtensionLoader::GetInstance();
-  embedded_a11y_extension_loader->InstallA11yHelperExtensionForReadingMode();
+  embedded_a11y_extension_loader->InstallExtensionWithId(
+      extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
   for (auto* const profile : profiles) {
     WaitForExtensionLoaded(profile,
-                           extension_misc::kEmbeddedA11yHelperExtensionId);
+                           extension_misc::kReadingModeGDocsHelperExtensionId);
   }
 
   // Remove the extension.
-  embedded_a11y_extension_loader->RemoveA11yHelperExtensionForReadingMode();
+  embedded_a11y_extension_loader->RemoveExtensionWithId(
+      extension_misc::kReadingModeGDocsHelperExtensionId);
   for (auto* const profile : profiles) {
-    WaitForExtensionUnloaded(profile,
-                             extension_misc::kEmbeddedA11yHelperExtensionId);
+    WaitForExtensionUnloaded(
+        profile, extension_misc::kReadingModeGDocsHelperExtensionId);
   }
 }
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
-                       InstallsOnIncognitoProfile) {
+                       // TODO(crbug.com/333813413): Re-enable this test
+                       DISABLED_InstallsOnIncognitoProfile) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   Browser* incognito =
-      CreateIncognitoBrowser(profile_manager->GetPrimaryUserProfile());
+      CreateIncognitoBrowser(profile_manager->GetLastUsedProfile());
   content::RunAllTasksUntilIdle();
 
   InstallAndWaitForExtensionLoaded(
-      incognito->profile(), extension_misc::kEmbeddedA11yHelperExtensionId,
-      extension_misc::kEmbeddedA11yHelperExtensionPath,
-      extension_misc::kEmbeddedA11yHelperManifestFilename,
-      /*should_localize=*/true);
+      incognito->profile(), extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
   RemoveAndWaitForExtensionUnloaded(
-      incognito->profile(), extension_misc::kEmbeddedA11yHelperExtensionId);
+      incognito->profile(), extension_misc::kReadingModeGDocsHelperExtensionId);
 }
 
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+// CreateGuestBrowser() is not supported for ChromeOS out of the box.
 IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
-                       InstallsOnGuestProfile) {
+                       // TODO(crbug.com/333813413): Re-enable this test
+                       DISABLED_InstallsOnGuestProfile) {
   Browser* guest_browser = CreateGuestBrowser();
   content::RunAllTasksUntilIdle();
 
   InstallAndWaitForExtensionLoaded(
-      guest_browser->profile(), extension_misc::kEmbeddedA11yHelperExtensionId,
+      guest_browser->profile(),
+      extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
+  RemoveAndWaitForExtensionUnloaded(
+      guest_browser->profile(),
+      extension_misc::kReadingModeGDocsHelperExtensionId);
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // !BUILDFLAG(IS_CHROMEOS_LACROS)
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
+                       InstallsExtensionOnLacros) {
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  const auto& profiles = profile_manager->GetLoadedProfiles();
+  ASSERT_GT(profiles.size(), 0u);
+  Profile* profile = profiles[0];
+
+  InstallAndWaitForExtensionLoaded(
+      profile, extension_misc::kEmbeddedA11yHelperExtensionId,
       extension_misc::kEmbeddedA11yHelperExtensionPath,
       extension_misc::kEmbeddedA11yHelperManifestFilename,
       /*should_localize=*/true);
   RemoveAndWaitForExtensionUnloaded(
-      guest_browser->profile(), extension_misc::kEmbeddedA11yHelperExtensionId);
+      profile, extension_misc::kEmbeddedA11yHelperExtensionId);
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/browser/apps/app_deduplication_service/app_deduplication_service.cc b/chrome/browser/apps/app_deduplication_service/app_deduplication_service.cc
index 9123d0f..44667f1e 100644
--- a/chrome/browser/apps/app_deduplication_service/app_deduplication_service.cc
+++ b/chrome/browser/apps/app_deduplication_service/app_deduplication_service.cc
@@ -290,13 +290,13 @@
         }
         entry = web_id.value();
       } else {
-        AppType source = package_id.value().app_type();
+        PackageType source = package_id.value().package_type();
         app_id = package_id.value().identifier();
-        if (source != AppType::kArc && source != AppType::kWeb) {
+        if (source != PackageType::kArc && source != PackageType::kWeb) {
           LOG(ERROR) << "Source is an unsupported type.";
           NOTREACHED();
         }
-        entry = Entry(app_id, source);
+        entry = Entry(app_id, ConvertPackageTypeToAppType(source).value());
       }
 
       // Initialize entry status.
diff --git a/chrome/browser/apps/app_preload_service/app_preload_service.cc b/chrome/browser/apps/app_preload_service/app_preload_service.cc
index ddd4cbe..e38934fe 100644
--- a/chrome/browser/apps/app_preload_service/app_preload_service.cc
+++ b/chrome/browser/apps/app_preload_service/app_preload_service.cc
@@ -213,11 +213,11 @@
 
 bool AppPreloadService::ShouldInstallApp(const PreloadAppDefinition& app) {
   // We preload android apps (when feature enabled) and web apps.
-  if (app.GetPlatform() == AppType::kArc) {
+  if (app.GetPlatform() == PackageType::kArc) {
     if (!base::FeatureList::IsEnabled(apps::kAppPreloadServiceEnableArcApps)) {
       return false;
     }
-  } else if (app.GetPlatform() != AppType::kWeb) {
+  } else if (app.GetPlatform() != PackageType::kWeb) {
     return false;
   }
 
@@ -235,7 +235,7 @@
   // retrying the flow after an install error for a different app.
 
   // TODO(crbug.com/329144520) Implement already installed check for android.
-  if (app.GetPlatform() == AppType::kArc) {
+  if (app.GetPlatform() == PackageType::kArc) {
     return true;
   }
 
diff --git a/chrome/browser/apps/app_preload_service/preload_app_definition.cc b/chrome/browser/apps/app_preload_service/preload_app_definition.cc
index 6e75327..4c9787c 100644
--- a/chrome/browser/apps/app_preload_service/preload_app_definition.cc
+++ b/chrome/browser/apps/app_preload_service/preload_app_definition.cc
@@ -27,11 +27,11 @@
   return app_proto_.name();
 }
 
-AppType PreloadAppDefinition::GetPlatform() const {
+PackageType PreloadAppDefinition::GetPlatform() const {
   if (package_id_.has_value()) {
-    return package_id_->app_type();
+    return package_id_->package_type();
   }
-  return AppType::kUnknown;
+  return PackageType::kUnknown;
 }
 
 bool PreloadAppDefinition::IsDefaultApp() const {
@@ -55,42 +55,42 @@
 }
 
 std::string PreloadAppDefinition::GetAndroidPackageName() const {
-  DCHECK_EQ(GetPlatform(), AppType::kArc);
+  DCHECK_EQ(GetPlatform(), PackageType::kArc);
   DCHECK(package_id_.has_value());
 
   return package_id_->identifier();
 }
 
 GURL PreloadAppDefinition::GetWebAppManifestUrl() const {
-  DCHECK_EQ(GetPlatform(), AppType::kWeb);
+  DCHECK_EQ(GetPlatform(), PackageType::kWeb);
 
   return GURL(app_proto_.web_extras().manifest_url());
 }
 
 GURL PreloadAppDefinition::GetWebAppOriginalManifestUrl() const {
-  DCHECK_EQ(GetPlatform(), AppType::kWeb);
+  DCHECK_EQ(GetPlatform(), PackageType::kWeb);
 
   return GURL(app_proto_.web_extras().original_manifest_url());
 }
 
 GURL PreloadAppDefinition::GetWebAppManifestId() const {
-  DCHECK_EQ(GetPlatform(), AppType::kWeb);
+  DCHECK_EQ(GetPlatform(), PackageType::kWeb);
   DCHECK(package_id_.has_value());
 
   return GURL(package_id_->identifier());
 }
 
 std::string PreloadAppDefinition::GetWebAppId() const {
-  DCHECK_EQ(GetPlatform(), AppType::kWeb);
+  DCHECK_EQ(GetPlatform(), PackageType::kWeb);
   return web_app::GenerateAppIdFromManifestId(GetWebAppManifestId());
 }
 
 AppInstallData PreloadAppDefinition::ToAppInstallData() const {
   AppInstallData result(package_id_.value());
   result.name = GetName();
-  if (GetPlatform() == AppType::kArc) {
+  if (GetPlatform() == PackageType::kArc) {
     // nothing.
-  } else if (GetPlatform() == AppType::kWeb) {
+  } else if (GetPlatform() == PackageType::kWeb) {
     auto& web_app_data = result.app_type_data.emplace<WebAppInstallData>();
     web_app_data.original_manifest_url = GetWebAppOriginalManifestUrl();
     web_app_data.proxied_manifest_url = GetWebAppManifestUrl();
@@ -108,10 +108,10 @@
   os << "- OEM: " << app.IsOemApp() << std::endl;
   os << "- Default: " << app.IsDefaultApp() << std::endl;
 
-  if (app.GetPlatform() == AppType::kArc) {
+  if (app.GetPlatform() == PackageType::kArc) {
     os << "- Android Extras:" << std::endl;
     os << "  - Package Name: " << app.GetAndroidPackageName() << std::endl;
-  } else if (app.GetPlatform() == AppType::kWeb) {
+  } else if (app.GetPlatform() == PackageType::kWeb) {
     os << "- Web Extras:" << std::endl;
     os << "  - Manifest URL: " << app.GetWebAppManifestUrl() << std::endl;
     os << "  - Original Manifest URL: " << app.GetWebAppOriginalManifestUrl()
diff --git a/chrome/browser/apps/app_preload_service/preload_app_definition.h b/chrome/browser/apps/app_preload_service/preload_app_definition.h
index 429d18e..83bbfd76 100644
--- a/chrome/browser/apps/app_preload_service/preload_app_definition.h
+++ b/chrome/browser/apps/app_preload_service/preload_app_definition.h
@@ -27,7 +27,7 @@
   ~PreloadAppDefinition();
 
   std::string GetName() const;
-  AppType GetPlatform() const;
+  PackageType GetPlatform() const;
   bool IsDefaultApp() const;
   bool IsOemApp() const;
   bool IsTestApp() const;
diff --git a/chrome/browser/apps/app_preload_service/preload_app_definition_unittest.cc b/chrome/browser/apps/app_preload_service/preload_app_definition_unittest.cc
index 601a62c..774120a 100644
--- a/chrome/browser/apps/app_preload_service/preload_app_definition_unittest.cc
+++ b/chrome/browser/apps/app_preload_service/preload_app_definition_unittest.cc
@@ -53,7 +53,7 @@
   proto::AppPreloadListResponse_App app;
 
   auto app_def = PreloadAppDefinition(app);
-  ASSERT_EQ(app_def.GetPlatform(), AppType::kUnknown);
+  ASSERT_EQ(app_def.GetPlatform(), PackageType::kUnknown);
 }
 
 TEST_F(PreloadAppDefinitionTest, GetPlatformMalformedPackageId) {
@@ -61,7 +61,7 @@
   app.set_package_id(":");
 
   auto app_def = PreloadAppDefinition(app);
-  ASSERT_EQ(app_def.GetPlatform(), AppType::kUnknown);
+  ASSERT_EQ(app_def.GetPlatform(), PackageType::kUnknown);
 }
 
 TEST_F(PreloadAppDefinitionTest, GetPlatformWeb) {
@@ -69,7 +69,7 @@
   app.set_package_id("web:https://example.com/");
 
   auto app_def = PreloadAppDefinition(app);
-  ASSERT_EQ(app_def.GetPlatform(), AppType::kWeb);
+  ASSERT_EQ(app_def.GetPlatform(), PackageType::kWeb);
 }
 
 TEST_F(PreloadAppDefinitionTest, IsOemAppWhenNotSet) {
@@ -229,7 +229,7 @@
   web_extras->set_manifest_url("https://cdn.com/manifest.json");
 
   AppInstallData expectation(
-      PackageId(AppType::kWeb, "https://www.example.com/index.html"));
+      PackageId(PackageType::kWeb, "https://www.example.com/index.html"));
   expectation.name = "Example App";
   WebAppInstallData& web_app_expecatation =
       expectation.app_type_data.emplace<WebAppInstallData>();
diff --git a/chrome/browser/apps/app_service/app_install/app_install_almanac_connector_unittest.cc b/chrome/browser/apps/app_service/app_install/app_install_almanac_connector_unittest.cc
index a1bf66c9..9825609aa 100644
--- a/chrome/browser/apps/app_service/app_install/app_install_almanac_connector_unittest.cc
+++ b/chrome/browser/apps/app_service/app_install/app_install_almanac_connector_unittest.cc
@@ -30,7 +30,7 @@
 
 namespace {
 
-const PackageId kTestPackageId(AppType::kWeb, "https://example.com/");
+const PackageId kTestPackageId(PackageType::kWeb, "https://example.com/");
 
 }  // namespace
 
@@ -66,9 +66,9 @@
         body = network::GetUploadData(request);
       }));
 
-  connector_.GetAppInstallInfo(PackageId(AppType::kWeb, "https://example.com/"),
-                               device_info, test_url_loader_factory_,
-                               base::DoNothing());
+  connector_.GetAppInstallInfo(
+      PackageId(PackageType::kWeb, "https://example.com/"), device_info,
+      test_url_loader_factory_, base::DoNothing());
 
   EXPECT_EQ(method, "POST");
   EXPECT_EQ(method_override_header, "GET");
@@ -131,7 +131,7 @@
   EXPECT_TRUE(response_future.Get().has_value());
 
   AppInstallData expected_data(
-      PackageId(AppType::kWeb, "https://example.com/"));
+      PackageId(PackageType::kWeb, "https://example.com/"));
   expected_data.name = "Example";
   expected_data.description = "Description.";
   expected_data.icon = AppInstallIcon{
diff --git a/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle.cc b/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle.cc
index 95d7668..e7ed7449 100644
--- a/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle.cc
+++ b/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle.cc
@@ -228,10 +228,7 @@
   }
 
   QueryParams query_params = ExtractQueryParams(url.query_piece());
-  // TODO(b/303350800): Generalize to work with all app types.
-  if (!query_params.package_id.has_value() ||
-      (query_params.package_id->app_type() != AppType::kWeb &&
-       query_params.package_id->app_type() != AppType::kBorealis)) {
+  if (!query_params.package_id.has_value()) {
     return content::NavigationThrottle::CANCEL_AND_IGNORE;
   }
 
diff --git a/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle_browsertest.cc b/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle_browsertest.cc
index eab7a92..9c7c00b5 100644
--- a/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle_browsertest.cc
+++ b/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle_browsertest.cc
@@ -147,7 +147,7 @@
     GURL manifest_url = embedded_test_server()->GetURL("/web_apps/basic.json");
     webapps::ManifestId manifest_id = start_url;
     webapps::AppId app_id = web_app::GenerateAppIdFromManifestId(manifest_id);
-    PackageId package_id(apps::AppType::kWeb, manifest_id.spec());
+    PackageId package_id(apps::PackageType::kWeb, manifest_id.spec());
 
     // Set Almanac server payload.
     response_map_[embedded_test_server()->GetURL("/v1/app-install")] = [&] {
@@ -211,24 +211,6 @@
   histograms.ExpectBucketCount("Apps.AppInstallParentWindowFound", true, 1);
   histograms.ExpectBucketCount("Apps.AppInstallParentWindowFound", false, 0);
 #endif
-
-  // Test whether already installed apps launch instead of going through the
-  // install flow again.
-  if (crosapi::AshSupportsCapabilities({"b/326167458"})) {
-    base::test::RepeatingTestFuture<apps::AppLaunchParams> future;
-    web_app::WebAppLaunchProcess::SetOpenApplicationCallbackForTesting(
-        future.GetCallback());
-
-    // Open install-app URI again.
-    EXPECT_TRUE(content::ExecJs(
-        browser()->tab_strip_model()->GetActiveWebContents(),
-        base::StringPrintf(
-            "window.open('almanac://install-app?package_id=%s');",
-            package_id.ToString().c_str())));
-
-    // This should launch the app instead of triggering installation.
-    EXPECT_EQ(future.Take().app_id, app_id);
-  }
 }
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle_unittest.cc b/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle_unittest.cc
index 96502de3..dd096e18 100644
--- a/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle_unittest.cc
+++ b/chrome/browser/apps/app_service/app_install/app_install_navigation_throttle_unittest.cc
@@ -42,26 +42,26 @@
   EXPECT_EQ(ExtractQueryParams("package_id=garbage"), QueryParams());
 
   EXPECT_EQ(ExtractQueryParams("package_id=web:identifier"),
-            QueryParams(PackageId(AppType::kWeb, "identifier"),
+            QueryParams(PackageId(PackageType::kWeb, "identifier"),
                         AppInstallSurface::kAppInstallUriUnknown));
 
   EXPECT_EQ(ExtractQueryParams("package_id=android:identifier"),
-            QueryParams(PackageId(AppType::kArc, "identifier"),
+            QueryParams(PackageId(PackageType::kArc, "identifier"),
                         AppInstallSurface::kAppInstallUriUnknown));
 
   EXPECT_EQ(ExtractQueryParams("package_id=garbage:identifier"), QueryParams());
 
   EXPECT_EQ(ExtractQueryParams("ignore&package_id=web:identifier"),
-            QueryParams(PackageId(AppType::kWeb, "identifier"),
+            QueryParams(PackageId(PackageType::kWeb, "identifier"),
                         AppInstallSurface::kAppInstallUriUnknown));
 
   EXPECT_EQ(
       ExtractQueryParams("ignore&package_id=web:identifier&ignore=as_well"),
-      QueryParams(PackageId(AppType::kWeb, "identifier"),
+      QueryParams(PackageId(PackageType::kWeb, "identifier"),
                   AppInstallSurface::kAppInstallUriUnknown));
 
   EXPECT_EQ(ExtractQueryParams("package_id=web:first&package_id=web:second"),
-            QueryParams(PackageId(AppType::kWeb, "second"),
+            QueryParams(PackageId(PackageType::kWeb, "second"),
                         AppInstallSurface::kAppInstallUriUnknown));
 
   EXPECT_EQ(
@@ -69,21 +69,21 @@
       QueryParams(std::nullopt, AppInstallSurface::kAppInstallUriUnknown));
 
   EXPECT_EQ(ExtractQueryParams("package_id=web:https://website.com/"),
-            QueryParams(PackageId(AppType::kWeb, "https://website.com/"),
+            QueryParams(PackageId(PackageType::kWeb, "https://website.com/"),
                         AppInstallSurface::kAppInstallUriUnknown));
 
   EXPECT_EQ(
       ExtractQueryParams(
           "package_id=web:https://website.com/?source=showoff&param2=value"),
       QueryParams(
-          PackageId(AppType::kWeb, "https://website.com/?source=showoff"),
+          PackageId(PackageType::kWeb, "https://website.com/?source=showoff"),
           AppInstallSurface::kAppInstallUriUnknown));
 
   EXPECT_EQ(
       ExtractQueryParams(
           "source=mall&package_id=web%3Ahttps%3A%2F%2Fwebsite.com%2F%"
           "3Fsource%3Dshowoff%26param2%3Dvalue"),
-      QueryParams(PackageId(AppType::kWeb,
+      QueryParams(PackageId(PackageType::kWeb,
                             "https://website.com/?source=showoff&param2=value"),
                   AppInstallSurface::kAppInstallUriMall));
 
@@ -100,32 +100,32 @@
       QueryParams(std::nullopt, AppInstallSurface::kAppInstallUriShowoff));
 
   EXPECT_EQ(ExtractQueryParams("package_id=web:identifier&source=garbage"),
-            QueryParams(PackageId(AppType::kWeb, "identifier"),
+            QueryParams(PackageId(PackageType::kWeb, "identifier"),
                         AppInstallSurface::kAppInstallUriUnknown));
 
   EXPECT_EQ(ExtractQueryParams("package_id=web:identifier&source=showoff"),
-            QueryParams(PackageId(AppType::kWeb, "identifier"),
+            QueryParams(PackageId(PackageType::kWeb, "identifier"),
                         AppInstallSurface::kAppInstallUriShowoff));
 
   EXPECT_EQ(ExtractQueryParams("package_id=web:identifier&source=mall"),
-            QueryParams(PackageId(AppType::kWeb, "identifier"),
+            QueryParams(PackageId(PackageType::kWeb, "identifier"),
                         AppInstallSurface::kAppInstallUriMall));
 
   EXPECT_EQ(ExtractQueryParams("package_id=web:identifier&source=getit"),
-            QueryParams(PackageId(AppType::kWeb, "identifier"),
+            QueryParams(PackageId(PackageType::kWeb, "identifier"),
                         AppInstallSurface::kAppInstallUriGetit));
 
   EXPECT_EQ(ExtractQueryParams("package_id=web:identifier&source=launcher"),
-            QueryParams(PackageId(AppType::kWeb, "identifier"),
+            QueryParams(PackageId(PackageType::kWeb, "identifier"),
                         AppInstallSurface::kAppInstallUriLauncher));
 
   EXPECT_EQ(ExtractQueryParams("source=mall&package_id=web:identifier"),
-            QueryParams(PackageId(AppType::kWeb, "identifier"),
+            QueryParams(PackageId(PackageType::kWeb, "identifier"),
                         AppInstallSurface::kAppInstallUriMall));
 
   EXPECT_EQ(
       ExtractQueryParams("source=mall&package_id=web:identifier&source=getit"),
-      QueryParams(PackageId(AppType::kWeb, "identifier"),
+      QueryParams(PackageId(PackageType::kWeb, "identifier"),
                   AppInstallSurface::kAppInstallUriGetit));
 }
 
diff --git a/chrome/browser/apps/app_service/app_install/app_install_service_ash.cc b/chrome/browser/apps/app_service/app_install/app_install_service_ash.cc
index 5f6b51d..82b7da5 100644
--- a/chrome/browser/apps/app_service/app_install/app_install_service_ash.cc
+++ b/chrome/browser/apps/app_service/app_install/app_install_service_ash.cc
@@ -15,27 +15,26 @@
 #include "chrome/browser/apps/app_service/app_install/app_install.pb.h"
 #include "chrome/browser/apps/app_service/app_install/app_install_almanac_connector.h"
 #include "chrome/browser/apps/app_service/app_install/app_install_types.h"
+#include "chrome/browser/apps/app_service/launch_utils.h"
 #include "chrome/browser/ash/borealis/borealis_game_install_flow.h"
 #include "chrome/browser/ash/crosapi/crosapi_ash.h"
 #include "chrome/browser/ash/crosapi/crosapi_manager.h"
 #include "chrome/browser/ash/crosapi/web_app_service_ash.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/ash/app_install/app_install.mojom.h"
-#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
-#include "components/user_manager/user_manager.h"
 // TODO(crbug.com/1488697): Remove circular dependency.
-#include "chrome/browser/apps/app_service/app_service_proxy.h"
-#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ui/webui/ash/app_install/app_install_dialog.h"  // nogncheck
 #include "chrome/browser/ui/webui/ash/app_install/app_install_page_handler.h"  // nogncheck
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
-#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
+#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/crosapi/mojom/web_app_service.mojom.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/package_id.h"
 #include "components/services/app_service/public/cpp/types_util.h"
+#include "components/user_manager/user_manager.h"
+#include "net/base/url_util.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "ui/gfx/native_widget_types.h"
 
@@ -72,21 +71,16 @@
   return AppInstallResult::kUnknown;
 }
 
-// TODO(b/330414871): AppInstallService shouldn't know about publisher specific
-// logic, remove the generation of app_ids.
-std::string GetAppId(const PackageId& package_id) {
-  CHECK_EQ(package_id.app_type(), AppType::kWeb);
-  // data->package_id.identifier() is the manifest ID for web apps.
-  return web_app::GenerateAppIdFromManifestId(GURL(package_id.identifier()));
-}
-
-void RecordInstallResult(AppInstallSurface surface, AppInstallResult result) {
+void RecordInstallResult(base::OnceClosure callback,
+                         AppInstallSurface surface,
+                         AppInstallResult result) {
   base::UmaHistogramEnumeration("Apps.AppInstallService.AppInstallResult",
                                 result);
   base::UmaHistogramEnumeration(
       base::StrCat({"Apps.AppInstallService.AppInstallResult.",
                     base::ToString(surface)}),
       result);
+  std::move(callback).Run();
 }
 
 }  // namespace
@@ -114,34 +108,70 @@
     std::move(InstallAppCallbackForTesting()).Run(package_id);
   }
 
+  base::OnceCallback<void(AppInstallResult)> result_callback =
+      base::BindOnce(&RecordInstallResult, std::move(callback), surface);
+
   if (!CanUserInstall()) {
-    RecordInstallResult(surface, AppInstallResult::kUserTypeNotPermitted);
-    std::move(callback).Run();
+    std::move(result_callback).Run(AppInstallResult::kUserTypeNotPermitted);
     return;
   }
 
-  if (MaybeLaunchApp(package_id)) {
-    RecordInstallResult(surface, AppInstallResult::kAppAlreadyInstalled);
-    std::move(callback).Run();
-    return;
+  switch (package_id.package_type()) {
+    case PackageType::kArc: {
+      constexpr char kPlayStoreAppDetailsPage[] =
+          "https://play.google.com/store/apps/details";
+      GURL url = net::AppendOrReplaceQueryParameter(
+          GURL(kPlayStoreAppDetailsPage), "id", package_id.identifier());
+      MaybeLaunchPreferredAppForUrl(&*profile_, url,
+                                    LaunchSource::kFromInstaller);
+      std::move(result_callback).Run(AppInstallResult::kUnknown);
+      return;
+    }
+    case PackageType::kWeb: {
+      // Observe for `anchor_window` being destroyed during async work.
+      std::unique_ptr<views::NativeWindowTracker> anchor_window_tracker;
+      if (anchor_window) {
+        anchor_window_tracker =
+            views::NativeWindowTracker::Create(*anchor_window);
+      }
+
+      FetchAppInstallData(
+          package_id,
+          base::BindOnce(&AppInstallServiceAsh::ShowDialogAndInstall,
+                         weak_ptr_factory_.GetWeakPtr(), surface, package_id,
+                         anchor_window, std::move(anchor_window_tracker),
+                         std::move(result_callback)));
+      return;
+    }
+    case PackageType::kBorealis: {
+      if (!base::FeatureList::IsEnabled(
+              ash::features::kAppInstallServiceUriBorealis)) {
+        std::move(result_callback)
+            .Run(AppInstallResult::kAppProviderNotAvailable);
+        return;
+      }
+
+      // Parse the Steam Game ID from the PackageId.
+      uint64_t steam_game_id;
+      if (!base::StringToUint64(package_id.identifier(), &steam_game_id)) {
+        std::move(result_callback).Run(AppInstallResult::kAppDataCorrupted);
+        return;
+      }
+
+      borealis::UserRequestedSteamGameInstall(&*profile_, steam_game_id);
+
+      // We've now launched the Borealis installer or the Steam Store
+      // website. We don't yet know whether that flow will result in a
+      // successfully installed game.
+      std::move(result_callback).Run(AppInstallResult::kUnknown);
+      return;
+    }
+    case PackageType::kChromeApp:
+    case PackageType::kUnknown:
+      // TODO(b/303350800): Generalize to work with all app types.
+      std::move(result_callback).Run(AppInstallResult::kAppTypeNotSupported);
+      return;
   }
-
-  // TODO(b/303350800): Generalize to work with all app types.
-  CHECK(package_id.app_type() == AppType::kWeb ||
-        package_id.app_type() == AppType::kBorealis);
-
-  // Observe for `anchor_window` being destroyed during async work.
-  std::unique_ptr<views::NativeWindowTracker> anchor_window_tracker;
-  if (anchor_window) {
-    anchor_window_tracker = views::NativeWindowTracker::Create(*anchor_window);
-  }
-
-  FetchAppInstallData(
-      package_id,
-      base::BindOnce(&AppInstallServiceAsh::ShowDialogAndInstall,
-                     weak_ptr_factory_.GetWeakPtr(), surface, package_id,
-                     anchor_window, std::move(anchor_window_tracker),
-                     std::move(callback)));
 }
 
 void AppInstallServiceAsh::InstallAppHeadless(
@@ -184,29 +214,6 @@
   return true;
 }
 
-bool AppInstallServiceAsh::MaybeLaunchApp(const PackageId& package_id) {
-  AppServiceProxy* proxy = AppServiceProxyFactory::GetForProfile(&*profile_);
-  if (!proxy) {
-    return false;
-  }
-  std::optional<std::string> app_id;
-  proxy->AppRegistryCache().ForEachApp(
-      [&app_id, package_id](const apps::AppUpdate& update) {
-        if (!app_id.has_value() && apps_util::IsInstalled(update.Readiness()) &&
-            update.InstallerPackageId() == package_id) {
-          app_id = update.AppId();
-        }
-      });
-
-  if (!app_id.has_value()) {
-    return false;
-  }
-
-  proxy->Launch(app_id.value(), /*event_flags=*/0,
-                LaunchSource::kFromInstaller);
-  return true;
-}
-
 void AppInstallServiceAsh::FetchAppInstallData(
     PackageId package_id,
     base::OnceCallback<void(std::optional<AppInstallData>)> data_callback) {
@@ -253,108 +260,67 @@
     PackageId expected_package_id,
     std::optional<gfx::NativeWindow> anchor_window,
     std::unique_ptr<views::NativeWindowTracker> anchor_window_tracker,
-    base::OnceClosure callback,
+    base::OnceCallback<void(AppInstallResult)> callback,
     std::optional<AppInstallData> data) {
-  std::optional<AppInstallResult> result =
-      [&]() -> std::optional<AppInstallResult> {
-    gfx::NativeWindow parent =
-        anchor_window.has_value() &&
-                !anchor_window_tracker->WasNativeWindowDestroyed()
-            ? anchor_window.value()
-            : nullptr;
+  gfx::NativeWindow parent =
+      anchor_window.has_value() &&
+              !anchor_window_tracker->WasNativeWindowDestroyed()
+          ? anchor_window.value()
+          : nullptr;
 
-    if (!data || data->package_id != expected_package_id) {
-      if (ash::app_install::AppInstallDialog::IsEnabled()) {
-        ash::app_install::AppInstallDialog::CreateDialog()->ShowNoAppError(
-            parent, base::BindOnce(&AppInstallServiceAsh::InstallApp,
-                                   weak_ptr_factory_.GetWeakPtr(), surface,
-                                   expected_package_id, anchor_window,
-                                   base::DoNothing()));
-      }
-      return data ? AppInstallResult::kAppDataCorrupted
-                  : AppInstallResult::kAlmanacFetchFailed;
+  if (!data || data->package_id != expected_package_id) {
+    if (ash::app_install::AppInstallDialog::IsEnabled()) {
+      ash::app_install::AppInstallDialog::CreateDialog()->ShowNoAppError(
+          parent, base::BindOnce(&AppInstallServiceAsh::InstallApp,
+                                 weak_ptr_factory_.GetWeakPtr(), surface,
+                                 expected_package_id, anchor_window,
+                                 base::DoNothing()));
     }
-
-    switch (expected_package_id.app_type()) {
-      case AppType::kWeb:
-        if (const auto* web_app_data =
-                absl::get_if<WebAppInstallData>(&data->app_type_data)) {
-          if (ash::app_install::AppInstallDialog::IsEnabled()) {
-            ash::app_install::mojom::DialogArgsPtr args =
-                ash::app_install::mojom::DialogArgs::New();
-            args->url = web_app_data->document_url;
-            args->name = data->name;
-            args->description = data->description;
-            args->icon_url = data->icon ? data->icon->url : GURL::EmptyGURL();
-            for (auto& screenshot : data->screenshots) {
-              auto dialog_screenshot =
-                  ash::app_install::mojom::Screenshot::New();
-              dialog_screenshot->url = screenshot.url;
-              dialog_screenshot->size = gfx::Size(screenshot.width_in_pixels,
-                                                  screenshot.height_in_pixels);
-              args->screenshots.push_back(std::move(dialog_screenshot));
-            }
-
-            webapps::AppId expected_app_id = GetAppId(data->package_id);
-            base::WeakPtr<ash::app_install::AppInstallDialog> dialog =
-                ash::app_install::AppInstallDialog::CreateDialog();
-            dialog->ShowApp(
-                &*profile_, parent, std::move(args),
-                data->icon ? data->icon->width_in_pixels : 0,
-                data->icon ? data->icon->is_masking_allowed : false,
-                expected_app_id,
-                base::BindOnce(&AppInstallServiceAsh::InstallIfDialogAccepted,
-                               weak_ptr_factory_.GetWeakPtr(), surface,
-                               expected_package_id, std::move(data).value(),
-                               dialog, std::move(callback)));
-            return std::nullopt;
-          }
-          // TODO(b/303350800): Delegate to a generic AppPublisher method
-          // instead of harboring app type specific logic here.
-          return InstallWebAppWithBrowserInstallDialog(
-              *profile_, web_app_data->document_url);
-        }
-        return AppInstallResult::kAppDataCorrupted;
-      case AppType::kBorealis:
-        if (!base::FeatureList::IsEnabled(
-                ash::features::kAppInstallServiceUriBorealis)) {
-          return AppInstallResult::kAppProviderNotAvailable;
-        }
-
-        // Parse the Steam Game ID from the PackageId.
-        uint64_t steam_game_id;
-        if (!base::StringToUint64(expected_package_id.identifier(),
-                                  &steam_game_id)) {
-          return AppInstallResult::kAppDataCorrupted;
-        }
-
-        borealis::UserRequestedSteamGameInstall(&*profile_, steam_game_id);
-
-        // We've now launched the Borealis installer or the Steam Store
-        // website. We don't yet know whether that flow will result in a
-        // successfully installed game.
-        return AppInstallResult::kUnknown;
-      case AppType::kArc:
-      case AppType::kBruschetta:
-      case AppType::kBuiltIn:
-      case AppType::kChromeApp:
-      case AppType::kCrostini:
-      case AppType::kExtension:
-      case AppType::kPluginVm:
-      case AppType::kRemote:
-      case AppType::kStandaloneBrowser:
-      case AppType::kStandaloneBrowserChromeApp:
-      case AppType::kStandaloneBrowserExtension:
-      case AppType::kSystemWeb:
-      case AppType::kUnknown:
-        return AppInstallResult::kAppTypeNotSupported;
-    }
-  }();
-
-  if (result.has_value()) {
-    RecordInstallResult(surface, result.value());
-    std::move(callback).Run();
+    std::move(callback).Run(data ? AppInstallResult::kAppDataCorrupted
+                                 : AppInstallResult::kAlmanacFetchFailed);
+    return;
   }
+
+  // The install dialog is only used for web apps currently.
+  CHECK_EQ(expected_package_id.package_type(), PackageType::kWeb);
+  const auto* web_app_data =
+      absl::get_if<WebAppInstallData>(&data->app_type_data);
+  if (!web_app_data) {
+    std::move(callback).Run(AppInstallResult::kAppDataCorrupted);
+    return;
+  }
+
+  if (!base::FeatureList::IsEnabled(
+          chromeos::features::kCrosWebAppInstallDialog) &&
+      !ash::app_install::AppInstallPageHandler::GetAutoAcceptForTesting()) {
+    // TODO(b/303350800): Delegate to a generic AppPublisher method
+    // instead of harboring app type specific logic here.
+    std::move(callback).Run(InstallWebAppWithBrowserInstallDialog(
+        *profile_, web_app_data->document_url));
+    return;
+  }
+
+  std::vector<ash::app_install::mojom::ScreenshotPtr> screenshots;
+  for (auto& screenshot : data->screenshots) {
+    auto dialog_screenshot = ash::app_install::mojom::Screenshot::New();
+    dialog_screenshot->url = screenshot.url;
+    dialog_screenshot->size =
+        gfx::Size(screenshot.width_in_pixels, screenshot.height_in_pixels);
+    screenshots.push_back(std::move(dialog_screenshot));
+  }
+
+  base::WeakPtr<ash::app_install::AppInstallDialog> dialog =
+      ash::app_install::AppInstallDialog::CreateDialog();
+  dialog->ShowApp(&*profile_, parent, expected_package_id, data->name,
+                  web_app_data->document_url, data->description,
+                  data->icon ? data->icon->url : GURL::EmptyGURL(),
+                  data->icon ? data->icon->width_in_pixels : 0,
+                  data->icon ? data->icon->is_masking_allowed : false,
+                  std::move(screenshots),
+                  base::BindOnce(&AppInstallServiceAsh::InstallIfDialogAccepted,
+                                 weak_ptr_factory_.GetWeakPtr(), surface,
+                                 expected_package_id, std::move(data).value(),
+                                 dialog, std::move(callback)));
 }
 
 void AppInstallServiceAsh::InstallIfDialogAccepted(
@@ -362,11 +328,10 @@
     PackageId expected_package_id,
     AppInstallData data,
     base::WeakPtr<ash::app_install::AppInstallDialog> dialog,
-    base::OnceClosure callback,
+    base::OnceCallback<void(AppInstallResult)> callback,
     bool dialog_accepted) {
   if (!dialog_accepted) {
-    RecordInstallResult(surface, AppInstallResult::kInstallDialogNotAccepted);
-    std::move(callback).Run();
+    std::move(callback).Run(AppInstallResult::kInstallDialogNotAccepted);
     return;
   }
   web_app_installer_.InstallApp(
@@ -381,25 +346,25 @@
     PackageId expected_package_id,
     AppInstallData data,
     base::WeakPtr<ash::app_install::AppInstallDialog> dialog,
-    base::OnceClosure callback,
+    base::OnceCallback<void(AppInstallResult)> callback,
     bool install_success) {
-  RecordInstallResult(surface, install_success
-                                   ? AppInstallResult::kSuccess
-                                   : AppInstallResult::kAppTypeInstallFailed);
   if (!dialog) {
-    std::move(callback).Run();
+    std::move(callback).Run(install_success
+                                ? AppInstallResult::kSuccess
+                                : AppInstallResult::kAppTypeInstallFailed);
     return;
   }
+
   if (install_success) {
-    std::string app_id = GetAppId(expected_package_id);
-    dialog->SetInstallSucceeded(&app_id);
-    std::move(callback).Run();
-  } else {
-    dialog->SetInstallFailed(base::BindOnce(
-        &AppInstallServiceAsh::InstallIfDialogAccepted,
-        weak_ptr_factory_.GetWeakPtr(), surface, expected_package_id,
-        std::move(data), dialog, std::move(callback)));
+    dialog->SetInstallSucceeded();
+    std::move(callback).Run(AppInstallResult::kSuccess);
+    return;
   }
+
+  dialog->SetInstallFailed(base::BindOnce(
+      &AppInstallServiceAsh::InstallIfDialogAccepted,
+      weak_ptr_factory_.GetWeakPtr(), surface, expected_package_id,
+      std::move(data), dialog, std::move(callback)));
 }
 
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/app_install/app_install_service_ash.h b/chrome/browser/apps/app_service/app_install/app_install_service_ash.h
index 91a9214..04de47c0 100644
--- a/chrome/browser/apps/app_service/app_install/app_install_service_ash.h
+++ b/chrome/browser/apps/app_service/app_install/app_install_service_ash.h
@@ -40,10 +40,9 @@
   kAppProviderNotAvailable = 4,
   kAppTypeNotSupported = 5,
   kInstallParametersInvalid = 6,
-  kAppAlreadyInstalled = 7,
-  kInstallDialogNotAccepted = 8,
-  kAppTypeInstallFailed = 9,
-  kUserTypeNotPermitted = 10,
+  kInstallDialogNotAccepted = 7,
+  kAppTypeInstallFailed = 8,
+  kUserTypeNotPermitted = 9,
   kMaxValue = kUserTypeNotPermitted,
 };
 
@@ -93,21 +92,21 @@
       PackageId expected_package_id,
       std::optional<gfx::NativeWindow> anchor_window,
       std::unique_ptr<views::NativeWindowTracker> anchor_window_tracker,
-      base::OnceClosure callback,
+      base::OnceCallback<void(AppInstallResult)> callback,
       std::optional<AppInstallData> data);
   void InstallIfDialogAccepted(
       AppInstallSurface surface,
       PackageId expected_package_id,
       AppInstallData data,
       base::WeakPtr<ash::app_install::AppInstallDialog> dialog,
-      base::OnceClosure callback,
+      base::OnceCallback<void(AppInstallResult)> callback,
       bool dialog_accepted);
   void ProcessInstallResult(
       AppInstallSurface surface,
       PackageId expected_package_id,
       AppInstallData data,
       base::WeakPtr<ash::app_install::AppInstallDialog> dialog,
-      base::OnceClosure callback,
+      base::OnceCallback<void(AppInstallResult)> callback,
       bool install_success);
 
   raw_ref<Profile> profile_;
diff --git a/chrome/browser/apps/app_service/app_install/app_install_service_ash_browsertest.cc b/chrome/browser/apps/app_service/app_install/app_install_service_ash_browsertest.cc
index 48c5fdd..a505fb7 100644
--- a/chrome/browser/apps/app_service/app_install/app_install_service_ash_browsertest.cc
+++ b/chrome/browser/apps/app_service/app_install/app_install_service_ash_browsertest.cc
@@ -17,9 +17,36 @@
 #include "components/services/app_service/public/cpp/package_id.h"
 #include "components/user_manager/user_names.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/test_navigation_observer.h"
 
 namespace apps {
 
+using AppInstallServiceAshBrowserTest = InProcessBrowserTest;
+
+IN_PROC_BROWSER_TEST_F(AppInstallServiceAshBrowserTest,
+                       InstallArcAppOpensPlayStore) {
+  base::HistogramTester histogram_tester;
+
+  content::TestNavigationObserver navigation_observer(
+      GURL("https://play.google.com/store/apps/details?id=com.android.chrome"));
+  navigation_observer.StartWatchingNewWebContents();
+
+  AppServiceProxyFactory::GetForProfile(browser()->profile())
+      ->AppInstallService()
+      .InstallApp(AppInstallSurface::kAppInstallUriUnknown,
+                  PackageId(PackageType::kArc, "com.android.chrome"),
+                  /*anchor_window=*/std::nullopt, base::DoNothing());
+
+  navigation_observer.Wait();
+
+  AppInstallResult expected_result = AppInstallResult::kUnknown;
+  histogram_tester.ExpectUniqueSample("Apps.AppInstallService.AppInstallResult",
+                                      expected_result, 1);
+  histogram_tester.ExpectUniqueSample(
+      "Apps.AppInstallService.AppInstallResult.AppInstallUriUnknown",
+      expected_result, 1);
+}
+
 class AppInstallServiceAshGuestBrowserTest
     : public InProcessBrowserTest,
       public testing::WithParamInterface<bool> {
@@ -56,7 +83,7 @@
   AppServiceProxyFactory::GetForProfile(browser()->profile())
       ->AppInstallService()
       .InstallApp(AppInstallSurface::kAppInstallUriUnknown,
-                  PackageId(AppType::kWeb, "test"),
+                  PackageId(PackageType::kWeb, "test"),
                   /*anchor_window=*/std::nullopt, run_loop.QuitClosure());
   run_loop.Run();
 
diff --git a/chrome/browser/apps/app_service/package_id_util.cc b/chrome/browser/apps/app_service/package_id_util.cc
index 66dfddbd9..a619cf1f 100644
--- a/chrome/browser/apps/app_service/package_id_util.cc
+++ b/chrome/browser/apps/app_service/package_id_util.cc
@@ -7,10 +7,14 @@
 #include <optional>
 #include <string>
 
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/app_update.h"
 #include "components/services/app_service/public/cpp/package_id.h"
+#include "components/services/app_service/public/cpp/types_util.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ash/apps/apk_web_app_service.h"
@@ -40,11 +44,32 @@
         apk_web_app_service->GetPackageNameForWebApp(
             update.AppId(), /*include_installing_apks=*/true);
     if (package_name.has_value()) {
-      return apps::PackageId(apps::AppType::kArc, package_name.value());
+      return apps::PackageId(apps::PackageType::kArc, package_name.value());
     }
   }
-  return apps::PackageId(update.AppType(), update.PublisherId());
+  return apps::PackageId(ConvertAppTypeToPackageType(update.AppType()).value(),
+                         update.PublisherId());
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+std::optional<std::string> GetAppWithPackageId(
+    Profile* profile,
+    const apps::PackageId& package_id) {
+  apps::AppServiceProxy* proxy =
+      apps::AppServiceProxyFactory::GetForProfile(profile);
+  if (!proxy) {
+    return std::nullopt;
+  }
+
+  std::optional<std::string> app_id;
+  proxy->AppRegistryCache().ForEachApp(
+      [&app_id, package_id](const apps::AppUpdate& update) {
+        if (!app_id.has_value() && IsInstalled(update.Readiness()) &&
+            update.InstallerPackageId() == package_id) {
+          app_id = update.AppId();
+        }
+      });
+  return app_id;
+}
+
 }  // namespace apps_util
diff --git a/chrome/browser/apps/app_service/package_id_util.h b/chrome/browser/apps/app_service/package_id_util.h
index b51441e..7f8f8b1b 100644
--- a/chrome/browser/apps/app_service/package_id_util.h
+++ b/chrome/browser/apps/app_service/package_id_util.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_APPS_APP_SERVICE_PACKAGE_ID_UTIL_H_
 
 #include <optional>
+#include <string>
 
 #include "build/chromeos_buildflags.h"
 
@@ -29,6 +30,10 @@
     const apps::AppUpdate& update);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+std::optional<std::string> GetAppWithPackageId(
+    Profile* profile,
+    const apps::PackageId& package_id);
+
 }  // namespace apps_util
 
 #endif  // CHROME_BROWSER_APPS_APP_SERVICE_PACKAGE_ID_UTIL_H_
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_almanac_connector_unittest.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_almanac_connector_unittest.cc
index a2af039..ef54049 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_almanac_connector_unittest.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_almanac_connector_unittest.cc
@@ -28,7 +28,7 @@
 
 namespace apps {
 
-const PackageId kTestPackageId(AppType::kArc, "test.package.name");
+const PackageId kTestPackageId(PackageType::kArc, "test.package.name");
 
 class PromiseAppAlmanacConnectorTest : public testing::Test {
  public:
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_icon_cache_unittest.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_icon_cache_unittest.cc
index 435234ae..c808b24 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_icon_cache_unittest.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_icon_cache_unittest.cc
@@ -21,7 +21,7 @@
 
 namespace apps {
 
-const PackageId kTestPackageId(AppType::kArc, "test.package.name");
+const PackageId kTestPackageId(PackageType::kArc, "test.package.name");
 
 SkColor kRed = SkColorSetRGB(255, 0, 0);
 SkColor kGreen = SkColorSetRGB(0, 255, 0);
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache_unittest.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache_unittest.cc
index 9bfef277..a266b6f 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache_unittest.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache_unittest.cc
@@ -16,7 +16,7 @@
 
 namespace apps {
 
-const PackageId kTestPackageId(AppType::kArc, "test.package.name");
+const PackageId kTestPackageId(PackageType::kArc, "test.package.name");
 
 class PromiseAppRegistryCacheTest : public testing::Test {
  public:
@@ -79,11 +79,11 @@
   EXPECT_EQ(cache()->GetAllPromiseApps().size(), 0u);
 
   // Register some promise apps.
-  auto package_id_1 = PackageId(AppType::kArc, "test1");
+  auto package_id_1 = PackageId(PackageType::kArc, "test1");
   auto promise_app_1 = std::make_unique<PromiseApp>(package_id_1);
   cache()->OnPromiseApp(std::move(promise_app_1));
 
-  auto package_id_2 = PackageId(AppType::kArc, "test2");
+  auto package_id_2 = PackageId(PackageType::kArc, "test2");
   auto promise_app_2 = std::make_unique<PromiseApp>(package_id_2);
   cache()->OnPromiseApp(std::move(promise_app_2));
 
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_service.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_service.cc
index c2ccc02..fc5378a 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_service.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_service.cc
@@ -77,13 +77,13 @@
     }
   )");
 
-apps::PromiseAppType GetPromiseAppType(apps::AppType promise_app_type,
+apps::PromiseAppType GetPromiseAppType(apps::PackageType promise_app_type,
                                        apps::AppType installed_app_type) {
-  if (promise_app_type == apps::AppType::kArc &&
+  if (promise_app_type == apps::PackageType::kArc &&
       installed_app_type == apps::AppType::kArc) {
     return apps::PromiseAppType::kArc;
   }
-  if (promise_app_type == apps::AppType::kArc &&
+  if (promise_app_type == apps::PackageType::kArc &&
       installed_app_type == apps::AppType::kWeb) {
     return apps::PromiseAppType::kTwa;
   }
@@ -183,7 +183,7 @@
   // Record metrics for app type, noting that the app type may differ between
   // the promise app and the installed app.
   RecordPromiseAppType(
-      GetPromiseAppType(package_id->app_type(), update.AppType()));
+      GetPromiseAppType(package_id->package_type(), update.AppType()));
 
   // Delete the promise app.
   PromiseAppPtr promise_app = std::make_unique<PromiseApp>(package_id.value());
@@ -214,7 +214,7 @@
 
   // Currently, updating install priority is only supported for ARC promise
   // apps.
-  if (promise_app->package_id.app_type() != AppType::kArc) {
+  if (promise_app->package_id.package_type() != PackageType::kArc) {
     return;
   }
 
@@ -333,7 +333,8 @@
       [&package_id, &is_registered](const AppUpdate& update) {
         // TODO(b/297296711): Update check for TWAs, which can have differing
         // package IDs.
-        if (update.AppType() != package_id.app_type()) {
+        if (ConvertPackageTypeToAppType(package_id.package_type()) !=
+            update.AppType()) {
           return;
         }
         if (update.PublisherId() != package_id.identifier()) {
@@ -359,7 +360,7 @@
 
 void PromiseAppService::OnApkWebAppInstallationFinished(
     const std::string& package_name) {
-  PackageId package_id(AppType::kArc, package_name);
+  PackageId package_id(PackageType::kArc, package_name);
 
   // Successful APK web app installations are already handled during a call to
   // observers via AppRegistryCache::OnAppUpdate which happens before this
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_service_unittest.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_service_unittest.cc
index 51327a09..e39f3aa 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_service_unittest.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_service_unittest.cc
@@ -42,7 +42,7 @@
 
 namespace apps {
 
-const PackageId kTestPackageId(AppType::kArc, "test.package.name");
+const PackageId kTestPackageId(PackageType::kArc, "test.package.name");
 
 class PromiseAppServiceTest : public testing::Test,
                               public PromiseAppRegistryCache::Observer {
@@ -324,7 +324,7 @@
 TEST_F(PromiseAppServiceTest, CompleteAppInstallationRemovesPromiseApp) {
   AppType app_type = AppType::kArc;
   std::string identifier = "test.com.example";
-  PackageId package_id(app_type, identifier);
+  PackageId package_id(PackageType::kArc, identifier);
 
   // Register test promise app.
   PromiseAppPtr promise_app = std::make_unique<PromiseApp>(package_id);
@@ -366,7 +366,7 @@
        SuppressPromiseAppsForAppsRegisteredInAppRegistryCache) {
   AppType app_type = AppType::kArc;
   std::string identifier = "test.com.example";
-  PackageId package_id(app_type, identifier);
+  PackageId package_id(PackageType::kArc, identifier);
 
   // Register sample test app in AppRegistryCache.
   apps::AppPtr app = std::make_unique<apps::App>(app_type, "asdfghjkl");
@@ -389,7 +389,7 @@
 TEST_F(PromiseAppServiceTest, AllowPromiseAppsForReinstallingApps) {
   AppType app_type = AppType::kArc;
   std::string identifier = "test.com.example";
-  PackageId package_id(app_type, identifier);
+  PackageId package_id(PackageType::kArc, identifier);
 
   // Register an app in AppRegistryCache, marked as uninstalled.
   apps::AppPtr app = std::make_unique<apps::App>(app_type, "asdfghjkl");
@@ -411,8 +411,7 @@
 
 TEST_F(PromiseAppServiceTest, WebOnlyTwaInstallationReplacesArcPromiseApp) {
   std::string package_name = "com.example.this";
-  apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, package_name);
+  PackageId package_id(PackageType::kArc, package_name);
   std::string app_id = "asdfghjkl";
 
   // Add a promise app to the cache.
@@ -441,9 +440,8 @@
 }
 
 TEST_F(PromiseAppServiceTest, FailedWebAppInstallationRemovesTwaPromiseApp) {
-  AppType app_type = AppType::kArc;
   std::string package_name = "test.com.example";
-  PackageId package_id(app_type, package_name);
+  PackageId package_id(PackageType::kArc, package_name);
   std::string app_id = "asdfghjkl";
 
   // Register test promise app.
@@ -465,8 +463,7 @@
 TEST_F(PromiseAppServiceTest, PromiseAppTypeRecorded) {
   // Case 1: ARC promise app.
   std::string arc_package_name = "com.arc.example";
-  apps::PackageId arc_package_id =
-      apps::PackageId(apps::AppType::kArc, arc_package_name);
+  PackageId arc_package_id(PackageType::kArc, arc_package_name);
   std::string arc_app_id = "qwerty";
 
   // Add an ARC package ID promise app to the cache.
@@ -492,8 +489,7 @@
   // Case 2: TWA promise app: should have an ARC package ID but results in a web
   // app.
   std::string twa_package_name = "com.twa.example";
-  apps::PackageId twa_package_id =
-      apps::PackageId(apps::AppType::kArc, twa_package_name);
+  PackageId twa_package_id(PackageType::kArc, twa_package_name);
   std::string twa_app_id = "asdfghjkl";
 
   // Add an ARC package ID promise app to the cache.
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_update_unittest.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_update_unittest.cc
index 76d3550..d28c3333 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_update_unittest.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_update_unittest.cc
@@ -12,7 +12,7 @@
 
 class PromiseAppUpdateTest : public testing::Test {
  public:
-  PackageId package_id = PackageId(AppType::kArc, "test.package.name");
+  PackageId package_id = PackageId(PackageType::kArc, "test.package.name");
 };
 
 TEST_F(PromiseAppUpdateTest, StateIsNonNull) {
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_web_apps_utils.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_web_apps_utils.cc
index 298c1287..e3a7592 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_web_apps_utils.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_web_apps_utils.cc
@@ -37,7 +37,8 @@
   }
 
   // Simulate the promise app installation stages.
-  apps::PackageId package_id(apps::AppType::kWeb, app->publisher_id.value());
+  apps::PackageId package_id(apps::PackageType::kWeb,
+                             app->publisher_id.value());
 
   // Register a promise app.
   apps::PromiseAppPtr promise_app =
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_wrapper_unittest.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_wrapper_unittest.cc
index 758acb1..3218c132 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_wrapper_unittest.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_wrapper_unittest.cc
@@ -13,7 +13,7 @@
 using PromiseAppWrapperTest = testing::Test;
 
 TEST_F(PromiseAppWrapperTest, ConversionSuccessful) {
-  PackageId package_id(AppType::kArc, "test.package.name");
+  PackageId package_id(PackageType::kArc, "test.package.name");
   GURL url("http://www.image.com");
 
   proto::PromiseAppResponse response;
diff --git a/chrome/browser/apps/app_service/publishers/arc_apps.cc b/chrome/browser/apps/app_service/publishers/arc_apps.cc
index 32cbc051..dd7c688 100644
--- a/chrome/browser/apps/app_service/publishers/arc_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/arc_apps.cc
@@ -1389,7 +1389,8 @@
                                                : InstallSource::kPlayStore);
 
   app->publisher_id = app_info.package_name;
-  app->installer_package_id = PackageId(AppType::kArc, app_info.package_name);
+  app->installer_package_id =
+      PackageId(PackageType::kArc, app_info.package_name);
   app->policy_ids = {app_info.package_name};
 
   if (update_icon) {
@@ -1579,8 +1580,8 @@
 void ArcApps::OnInstallationStarted(const std::string& package_name) {
   if (ash::features::ArePromiseIconsEnabled() &&
       ArcVersionEligibleForPromiseIcons()) {
-    PromiseAppPtr promise_app =
-        AppPublisher::MakePromiseApp(PackageId(AppType::kArc, package_name));
+    PromiseAppPtr promise_app = AppPublisher::MakePromiseApp(
+        PackageId(PackageType::kArc, package_name));
 
     // All ARC installations start as "Pending".
     promise_app->status = PromiseStatus::kPending;
@@ -1591,7 +1592,7 @@
 void ArcApps::OnInstallationProgressChanged(const std::string& package_name,
                                             float progress) {
   if (ash::features::ArePromiseIconsEnabled()) {
-    PackageId package_id = PackageId(AppType::kArc, package_name);
+    PackageId package_id = PackageId(PackageType::kArc, package_name);
     const PromiseApp* existing_promise_app =
         proxy()->PromiseAppRegistryCache()->GetPromiseApp(package_id);
     if (!existing_promise_app) {
@@ -1617,7 +1618,7 @@
 void ArcApps::OnInstallationActiveChanged(const std::string& package_name,
                                           bool active) {
   if (ash::features::ArePromiseIconsEnabled()) {
-    PackageId package_id(AppType::kArc, package_name);
+    PackageId package_id(PackageType::kArc, package_name);
     if (!proxy()->PromiseAppRegistryCache()->HasPromiseApp(package_id)) {
       LOG(ERROR) << "Cannot update installation active status for "
                  << package_name
@@ -1640,7 +1641,7 @@
     }
     // Remove the promise app of any failed installation or non-launchable
     // package.
-    PackageId package_id(AppType::kArc, package_name);
+    PackageId package_id(PackageType::kArc, package_name);
     if (!proxy()->PromiseAppRegistryCache()->HasPromiseApp(package_id)) {
       return;
     }
diff --git a/chrome/browser/apps/app_service/publishers/arc_apps_unittest.cc b/chrome/browser/apps/app_service/publishers/arc_apps_unittest.cc
index 386dfa6..fd8d72c4 100644
--- a/chrome/browser/apps/app_service/publishers/arc_apps_unittest.cc
+++ b/chrome/browser/apps/app_service/publishers/arc_apps_unittest.cc
@@ -71,7 +71,8 @@
 namespace {
 
 const char kTestPackageName[] = "com.example.this";
-const apps::PackageId kTestPackageId(apps::AppType::kArc, "com.example.this");
+const apps::PackageId kTestPackageId(apps::PackageType::kArc,
+                                     "com.example.this");
 
 std::vector<arc::IntentFilter> CreateFilterList(
     const std::string& package_name,
diff --git a/chrome/browser/apps/app_service/publishers/borealis_apps.cc b/chrome/browser/apps/app_service/publishers/borealis_apps.cc
index beded064..b00e323 100644
--- a/chrome/browser/apps/app_service/publishers/borealis_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/borealis_apps.cc
@@ -213,8 +213,8 @@
     // ID.
     std::optional<int> app_id = borealis::ParseSteamGameId(registration.Exec());
     if (app_id) {
-      app->installer_package_id =
-          PackageId(AppType::kBorealis, base::NumberToString(app_id.value()));
+      app->installer_package_id = PackageId(
+          PackageType::kBorealis, base::NumberToString(app_id.value()));
     }
   }
 }
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 5e4e397..1badcbe 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -1261,6 +1261,8 @@
     "file_manager/zip_io_task.cc",
     "file_manager/zip_io_task.h",
     "file_system_provider/abort_callback.h",
+    "file_system_provider/cloud_file_info.cc",
+    "file_system_provider/cloud_file_info.h",
     "file_system_provider/cloud_file_system.cc",
     "file_system_provider/cloud_file_system.h",
     "file_system_provider/content_cache/cache_file_context.cc",
@@ -1273,6 +1275,8 @@
     "file_system_provider/content_cache/content_cache_impl.h",
     "file_system_provider/content_cache/content_lru_cache.cc",
     "file_system_provider/content_cache/content_lru_cache.h",
+    "file_system_provider/content_cache/context_database.cc",
+    "file_system_provider/content_cache/context_database.h",
     "file_system_provider/extension_provider.cc",
     "file_system_provider/extension_provider.h",
     "file_system_provider/fileapi/backend_delegate.cc",
@@ -3832,6 +3836,7 @@
     "//chromeos/ash/components/metrics",
     "//chromeos/ash/components/mojo_service_manager/mojom",
     "//chromeos/ash/components/multidevice",
+    "//chromeos/ash/components/nearby/common/connections_manager:connections_manager",
     "//chromeos/ash/components/nearby/presence",
     "//chromeos/ash/components/nearby/presence/conversions",
     "//chromeos/ash/components/network",
@@ -5008,7 +5013,6 @@
     "//base/test:test_support",
     "//build:chromeos_buildflags",
     "//chrome/browser",
-    "//chrome/browser/nearby_sharing/public/cpp:test_support",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/ui",
     "//chrome/browser/web_applications",
@@ -5032,6 +5036,7 @@
     "//chromeos/ash/components/install_attributes:test_support",
     "//chromeos/ash/components/login/auth",
     "//chromeos/ash/components/login/auth/public:authpublic",
+    "//chromeos/ash/components/nearby/common/connections_manager:test_support",
     "//chromeos/ash/components/quick_start:test_support",
     "//chromeos/ash/components/settings",
     "//chromeos/ash/components/system",
@@ -5194,6 +5199,7 @@
     "../ui/webui/ash/settings/pages/multidevice/multidevice_handler_unittest.cc",
     "../ui/webui/ash/settings/pages/multidevice/multidevice_section_unittest.cc",
     "../ui/webui/ash/settings/pages/os_settings_section_unittest.cc",
+    "../ui/webui/ash/settings/pages/personalization/personalization_section_unittest.cc",
     "../ui/webui/ash/settings/pages/privacy/app_permission_handler_unittest.cc",
     "../ui/webui/ash/settings/pages/privacy/metrics_consent_handler_unittest.cc",
     "../ui/webui/ash/settings/pages/privacy/privacy_hub_handler_unittest.cc",
@@ -5641,6 +5647,7 @@
     "file_system_provider/content_cache/cache_manager_impl_unittest.cc",
     "file_system_provider/content_cache/content_cache_impl_unittest.cc",
     "file_system_provider/content_cache/content_lru_cache_unittest.cc",
+    "file_system_provider/content_cache/context_database_unittest.cc",
     "file_system_provider/fake_registry.cc",
     "file_system_provider/fake_registry.h",
     "file_system_provider/fileapi/buffering_file_stream_reader_unittest.cc",
diff --git a/chrome/browser/ash/DEPS b/chrome/browser/ash/DEPS
index 0dd8bd37..efe3a0a 100644
--- a/chrome/browser/ash/DEPS
+++ b/chrome/browser/ash/DEPS
@@ -647,6 +647,6 @@
   ],
   "mahi_ui_browsertest\.cc": [
     "+chrome/browser/ui/views/mahi/mahi_menu_view.h",
-    "+chrome/browser/ui/views/mahi/mahi_menu_view_ids.h",
+    "+chrome/browser/ui/views/mahi/mahi_menu_constants.h",
   ],
 }
diff --git a/chrome/browser/ash/accessibility/DEPS b/chrome/browser/ash/accessibility/DEPS
index 4d9b2577..6076f79 100644
--- a/chrome/browser/ash/accessibility/DEPS
+++ b/chrome/browser/ash/accessibility/DEPS
@@ -18,28 +18,19 @@
   "+chrome/browser/accessibility",
   "+chrome/browser/apps/app_service",
   "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
   "+chrome/browser/extensions/api/braille_display_private",
+  "+chrome/browser/extensions/component_loader.h",
   "+chrome/browser/extensions/error_console",
+  "+chrome/browser/extensions/extension_apitest.h",
+  "+chrome/browser/extensions/extension_browsertest.h",
+  "+chrome/browser/extensions/extension_service.h",
   "+chrome/browser/lifetime",
   "+chrome/browser/prefs",
   "+chrome/browser/profiles",
   "+chrome/browser/speech",
   "+chrome/browser/ui/ash",
   "+chrome/browser/ui/aura/accessibility",
-  "+chrome/browser/ui/tabs",
-  "+chrome/common/extensions",
-  "+chrome/grit",
-  "+chrome/test/base",
-  "+chrome/browser/browser_process.h",
-  "+chrome/common/chrome_paths.h",
-  "+chrome/common/chrome_switches.h",
-  "+chrome/common/pref_names.h",
-  "+chrome/common/url_constants.h",
-  "+chrome/common/webui_url_constants.h",
-  "+chrome/browser/extensions/component_loader.h",
-  "+chrome/browser/extensions/extension_apitest.h",
-  "+chrome/browser/extensions/extension_browsertest.h",
-  "+chrome/browser/extensions/extension_service.h",
   "+chrome/browser/ui/browser_commands.h",
   "+chrome/browser/ui/browser.h",
   "+chrome/browser/ui/browser_list.h",
@@ -47,8 +38,17 @@
   "+chrome/browser/ui/chrome_pages.h",
   "+chrome/browser/ui/settings_window_manager_chromeos.h",
   "+chrome/browser/ui/singleton_tabs.h",
+  "+chrome/browser/ui/tabs",
   "+chrome/browser/ui/view_ids.h",
   "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/url_constants.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
 ]
 
 specific_include_rules = {
diff --git a/chrome/browser/ash/accessibility/facegaze_browsertest.cc b/chrome/browser/ash/accessibility/facegaze_browsertest.cc
index 85d3ade..4007bd8 100644
--- a/chrome/browser/ash/accessibility/facegaze_browsertest.cc
+++ b/chrome/browser/ash/accessibility/facegaze_browsertest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <optional>
+
 #include "ash/shell.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/accessibility/accessibility_feature_browsertest.h"
@@ -13,9 +15,98 @@
 #include "ui/display/test/display_manager_test_api.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/point_f.h"
 
 namespace ash {
 
+using CursorSpeeds = FaceGazeTestUtils::CursorSpeeds;
+using MockFaceLandmarkerResult = FaceGazeTestUtils::MockFaceLandmarkerResult;
+
+namespace {
+
+const char* kDefaultDisplaySize = "1200x800";
+
+// A class that helps initialize FaceGaze with a configuration.
+class Config {
+ public:
+  Config() = default;
+  ~Config() = default;
+  Config(const Config&) = delete;
+  Config& operator=(const Config&) = delete;
+
+  // Returns a Config that sets required properties to default values.
+  Config& AsDefault() {
+    forehead_location_ = gfx::PointF(0.1, 0.2);
+    cursor_location_ = gfx::Point(600, 400);
+    buffer_size_ = 1;
+    use_cursor_acceleration_ = false;
+    return *this;
+  }
+
+  Config& WithForeheadLocation(const gfx::PointF& location) {
+    forehead_location_ = location;
+    return *this;
+  }
+
+  Config& WithCursorLocation(const gfx::Point& location) {
+    cursor_location_ = location;
+    return *this;
+  }
+
+  Config& WithBufferSize(int size) {
+    buffer_size_ = size;
+    return *this;
+  }
+
+  Config& WithCursorAcceleration(bool acceleration) {
+    use_cursor_acceleration_ = acceleration;
+    return *this;
+  }
+
+  Config& WithGesturesToMacros(const base::Value::Dict& gestures_to_macros) {
+    gestures_to_macros_ = gestures_to_macros.Clone();
+    return *this;
+  }
+
+  Config& WithGestureConfidences(const base::Value::Dict& gesture_confidences) {
+    gesture_confidences_ = gesture_confidences.Clone();
+    return *this;
+  }
+
+  Config& WithCursorSpeeds(const CursorSpeeds& speeds) {
+    cursor_speeds_ = speeds;
+    return *this;
+  }
+
+  const gfx::PointF& forehead_location() const { return forehead_location_; }
+  const gfx::Point& cursor_location() const { return cursor_location_; }
+  int buffer_size() const { return buffer_size_; }
+  bool use_cursor_acceleration() const { return use_cursor_acceleration_; }
+  const std::optional<base::Value::Dict>& gestures_to_macros() const {
+    return gestures_to_macros_;
+  }
+  const std::optional<base::Value::Dict>& gesture_confidences() const {
+    return gesture_confidences_;
+  }
+  const std::optional<CursorSpeeds>& cursor_speeds() const {
+    return cursor_speeds_;
+  }
+
+ private:
+  // Required properties.
+  gfx::PointF forehead_location_;
+  gfx::Point cursor_location_;
+  int buffer_size_;
+  bool use_cursor_acceleration_;
+
+  // Optional properties.
+  std::optional<base::Value::Dict> gestures_to_macros_;
+  std::optional<base::Value::Dict> gesture_confidences_;
+  std::optional<CursorSpeeds> cursor_speeds_;
+};
+
+}  // namespace
+
 class FaceGazeIntegrationTest : public AccessibilityFeatureBrowserTest {
  public:
   FaceGazeIntegrationTest() = default;
@@ -37,40 +128,55 @@
     event_generator_ = std::make_unique<ui::test::EventGenerator>(
         Shell::Get()->GetPrimaryRootWindow());
     display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
-        .UpdateDisplay("1200x800");
+        .UpdateDisplay(kDefaultDisplaySize);
 
     // Initialize FaceGaze.
     utils_->EnableFaceGaze();
     utils_->CreateFaceLandmarker();
-    utils_->InitializeBufferSizeAndAcceleration(1);
-    utils_->InitializeGesturesToMacros(
-        base::Value::Dict().Set("jawOpen", /*RESET_CURSOR*/ 37));
-    SetMouseSourceDeviceId(1);
-    // By default the mouse is placed at the center of the screen. To initialize
-    // FaceGaze at the center of the screen, move the mouse somewhere, then
-    // move it back to the center.
-    SendMouseMoveTo(gfx::Point(0, 0));
-    utils_->WaitForMousePosition(gfx::Point(0, 0));
-    SendMouseMoveTo(gfx::Point(600, 400));
-    utils_->WaitForMousePosition(gfx::Point(600, 400));
+  }
+
+  void ConfigureFaceGaze(const Config& config) {
+    // Set optional configuration properties.
+    if (config.cursor_speeds().has_value()) {
+      utils_->SetCursorSpeeds(config.cursor_speeds().value());
+    }
+    if (config.gestures_to_macros().has_value()) {
+      utils_->SetGesturesToMacros(config.gestures_to_macros().value());
+    }
+    if (config.gesture_confidences().has_value()) {
+      utils_->SetGestureConfidences(config.gesture_confidences().value());
+    }
+
+    // Set required configuration properties.
+    utils_->SetBufferSize(config.buffer_size());
+    utils_->SetCursorAcceleration(config.use_cursor_acceleration());
+
+    // By default the cursor is placed at the center of the screen. To
+    // initialize FaceGaze, move the cursor somewhere, then move it to the
+    // location specified by the config.
+    event_generator_->set_mouse_source_device_id(1);
+    MoveMouseTo(gfx::Point(0, 0));
+    AssertCursorAt(gfx::Point(0, 0));
+    MoveMouseTo(config.cursor_location());
+    AssertCursorAt(config.cursor_location());
 
     // No matter the starting location, the cursor position won't change
     // initially, and upcoming forehead locations will be computed relative to
     // this.
-    FaceGazeTestUtils::MockFaceLandmarkerResult result;
-    result.SetNormalizedForeheadLocation(0.1, 0.2);
-    utils_->ProcessFaceLandmarkerResult(result);
+    utils_->ProcessFaceLandmarkerResult(
+        MockFaceLandmarkerResult().WithNormalizedForeheadLocation(
+            config.forehead_location().x(), config.forehead_location().y()));
     utils_->TriggerMouseControllerInterval();
-    ASSERT_EQ(gfx::Point(600, 400),
-              display::Screen::GetScreen()->GetCursorScreenPoint());
+    AssertCursorAt(config.cursor_location());
   }
 
-  void SendMouseMoveTo(const gfx::Point& location) {
+  void MoveMouseTo(const gfx::Point& location) {
     event_generator_->MoveMouseTo(location.x(), location.y());
   }
 
-  void SetMouseSourceDeviceId(int id) {
-    event_generator_->set_mouse_source_device_id(id);
+  void AssertCursorAt(const gfx::Point& location) {
+    utils_->WaitForCursorPosition(location);
+    ASSERT_EQ(location, display::Screen::GetScreen()->GetCursorScreenPoint());
   }
 
   FaceGazeTestUtils* utils() { return utils_.get(); }
@@ -81,32 +187,75 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, UpdateMouseLocation) {
-  FaceGazeTestUtils::MockFaceLandmarkerResult result;
-  result.SetNormalizedForeheadLocation(0.11, 0.21);
-  utils()->ProcessFaceLandmarkerResult(result);
-  utils()->TriggerMouseControllerInterval();
+IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, UpdateCursorLocation) {
+  ConfigureFaceGaze(Config().AsDefault());
 
-  // Verify mouse location.
-  ASSERT_EQ(gfx::Point(360, 560),
-            display::Screen::GetScreen()->GetCursorScreenPoint());
+  // Move cursor using forehead.
+  utils()->ProcessFaceLandmarkerResult(
+      MockFaceLandmarkerResult().WithNormalizedForeheadLocation(0.11, 0.21));
+  utils()->TriggerMouseControllerInterval();
+  AssertCursorAt(gfx::Point(360, 560));
 }
 
 IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, ResetCursor) {
-  // Move mouse to location.
-  FaceGazeTestUtils::MockFaceLandmarkerResult move_mouse_result;
-  move_mouse_result.SetNormalizedForeheadLocation(0.11, 0.21);
-  utils()->ProcessFaceLandmarkerResult(move_mouse_result);
-  utils()->TriggerMouseControllerInterval();
-  ASSERT_EQ(gfx::Point(360, 560),
-            display::Screen::GetScreen()->GetCursorScreenPoint());
+  ConfigureFaceGaze(
+      Config()
+          .AsDefault()
+          .WithGesturesToMacros(
+              base::Value::Dict().Set("jawOpen", /*RESET_CURSOR*/ 37))
+          .WithGestureConfidences(base::Value::Dict().Set("jawOpen", 70)));
 
-  // Reset the mouse to the center of the screen using a gesture.
-  FaceGazeTestUtils::MockFaceLandmarkerResult gesture_result;
-  gesture_result.AddGestureWithConfidence("jawOpen", 0.9);
-  utils()->ProcessFaceLandmarkerResult(gesture_result);
-  ASSERT_EQ(gfx::Point(600, 400),
-            display::Screen::GetScreen()->GetCursorScreenPoint());
+  // Move cursor.
+  utils()->ProcessFaceLandmarkerResult(
+      MockFaceLandmarkerResult().WithNormalizedForeheadLocation(0.11, 0.21));
+  utils()->TriggerMouseControllerInterval();
+  AssertCursorAt(gfx::Point(360, 560));
+
+  // Reset the cursor to the center of the screen using a gesture.
+  utils()->ProcessFaceLandmarkerResult(
+      MockFaceLandmarkerResult().WithGesture("jawOpen", 90));
+  AssertCursorAt(gfx::Point(600, 400));
+}
+
+IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest,
+                       IgnoreGesturesWithLowConfidence) {
+  ConfigureFaceGaze(
+      Config()
+          .AsDefault()
+          .WithGesturesToMacros(
+              base::Value::Dict().Set("jawOpen", /*RESET_CURSOR*/ 37))
+          .WithGestureConfidences(base::Value::Dict().Set("jawOpen", 100)));
+
+  // Move cursor.
+  utils()->ProcessFaceLandmarkerResult(
+      MockFaceLandmarkerResult().WithNormalizedForeheadLocation(0.11, 0.21));
+  utils()->TriggerMouseControllerInterval();
+  AssertCursorAt(gfx::Point(360, 560));
+
+  // Attempt to reset the cursor to the center of the screen using a gesture.
+  // This gesture will be ignored because the gesture doesn't have high enough
+  // confidence.
+  utils()->ProcessFaceLandmarkerResult(
+      MockFaceLandmarkerResult().WithGesture("jawOpen", 90));
+  AssertCursorAt(gfx::Point(360, 560));
+}
+
+IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest,
+                       UpdateCursorLocationWithSpeed1) {
+  ConfigureFaceGaze(Config().AsDefault().WithCursorSpeeds(
+      {/*up=*/1, /*down=*/1, /*left=*/1, /*right=*/1}));
+
+  // With cursor acceleration off and buffer size 1, one-pixel head movements
+  // correspond to one-pixel changes on screen.
+  double px = 1.0 / 1200;
+  double py = 1.0 / 800;
+  for (int i = 1; i < 10; ++i) {
+    utils()->ProcessFaceLandmarkerResult(
+        MockFaceLandmarkerResult().WithNormalizedForeheadLocation(
+            0.1 + px * i, 0.2 + py * i));
+    utils()->TriggerMouseControllerInterval();
+    AssertCursorAt(gfx::Point(600 - i, 400 + i));
+  }
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ash/accessibility/facegaze_test_utils.cc b/chrome/browser/ash/accessibility/facegaze_test_utils.cc
index b50561a..6f4c997 100644
--- a/chrome/browser/ash/accessibility/facegaze_test_utils.cc
+++ b/chrome/browser/ash/accessibility/facegaze_test_utils.cc
@@ -43,18 +43,28 @@
 FaceGazeTestUtils::MockFaceLandmarkerResult::~MockFaceLandmarkerResult() =
     default;
 
-void FaceGazeTestUtils::MockFaceLandmarkerResult::SetNormalizedForeheadLocation(
-    double x, double y) {
+FaceGazeTestUtils::MockFaceLandmarkerResult&
+FaceGazeTestUtils::MockFaceLandmarkerResult::WithNormalizedForeheadLocation(
+    double x,
+    double y) {
   forehead_location_.Set("x", x);
   forehead_location_.Set("y", y);
+  return *this;
 }
 
-void FaceGazeTestUtils::MockFaceLandmarkerResult::AddGestureWithConfidence(
+FaceGazeTestUtils::MockFaceLandmarkerResult&
+FaceGazeTestUtils::MockFaceLandmarkerResult::WithGesture(
     const std::string& gesture,
-    double confidence) {
-  recognized_gestures_.Append(base::Value::Dict()
-                                  .Set("categoryName", gesture)
-                                  .Set("score", confidence));
+    int confidence) {
+  // For readability and consistency with the gesture confidence pref, this
+  // method accepts confidence values [0, 100]. However, the FaceLandmarker
+  // receives confidence scores as values [0, 1], so we need to convert the
+  // confidence to a decimal before processing it.
+  recognized_gestures_.Append(
+      base::Value::Dict()
+          .Set("categoryName", gesture)
+          .Set("score", static_cast<double>(confidence) / 100.0));
+  return *this;
 }
 
 FaceGazeTestUtils::FaceGazeTestUtils() = default;
@@ -129,27 +139,49 @@
   ExecuteAccessibilityCommonScript(script);
 }
 
-void FaceGazeTestUtils::WaitForMousePosition(const gfx::Point& location) {
+void FaceGazeTestUtils::WaitForCursorPosition(const gfx::Point& location) {
   std::string script =
-      base::StringPrintf("faceGazeTestSupport.waitForMouseLocation(%d, %d);",
+      base::StringPrintf("faceGazeTestSupport.waitForCursorLocation(%d, %d);",
                          location.x(), location.y());
   ExecuteAccessibilityCommonScript(script);
 }
 
-void FaceGazeTestUtils::InitializeBufferSizeAndAcceleration(int size) {
-  GetPrefs()->SetInteger(prefs::kAccessibilityFaceGazeCursorSmoothing, size);
-  GetPrefs()->SetBoolean(prefs::kAccessibilityFaceGazeCursorUseAcceleration,
-                         false);
+void FaceGazeTestUtils::SetCursorSpeeds(const CursorSpeeds& speeds) {
+  GetPrefs()->SetInteger(prefs::kAccessibilityFaceGazeCursorSpeedUp, speeds.up);
+  GetPrefs()->SetInteger(prefs::kAccessibilityFaceGazeCursorSpeedDown,
+                         speeds.down);
+  GetPrefs()->SetInteger(prefs::kAccessibilityFaceGazeCursorSpeedLeft,
+                         speeds.left);
+  GetPrefs()->SetInteger(prefs::kAccessibilityFaceGazeCursorSpeedRight,
+                         speeds.right);
   GetPrefs()->CommitPendingWrite();
 }
 
-void FaceGazeTestUtils::InitializeGesturesToMacros(
+void FaceGazeTestUtils::SetBufferSize(int size) {
+  GetPrefs()->SetInteger(prefs::kAccessibilityFaceGazeCursorSmoothing, size);
+  GetPrefs()->CommitPendingWrite();
+}
+
+void FaceGazeTestUtils::SetCursorAcceleration(bool use_acceleration) {
+  GetPrefs()->SetBoolean(prefs::kAccessibilityFaceGazeCursorUseAcceleration,
+                         use_acceleration);
+  GetPrefs()->CommitPendingWrite();
+}
+
+void FaceGazeTestUtils::SetGesturesToMacros(
     const base::Value::Dict& gestures_to_macros) {
   GetPrefs()->SetDict(prefs::kAccessibilityFaceGazeGesturesToMacros,
                       gestures_to_macros.Clone());
   GetPrefs()->CommitPendingWrite();
 }
 
+void FaceGazeTestUtils::SetGestureConfidences(
+    const base::Value::Dict& gesture_confidences) {
+  GetPrefs()->SetDict(prefs::kAccessibilityFaceGazeGesturesToConfidence,
+                      gesture_confidences.Clone());
+  GetPrefs()->CommitPendingWrite();
+}
+
 void FaceGazeTestUtils::ProcessFaceLandmarkerResult(
     const MockFaceLandmarkerResult& result) {
   std::string forehead_location_json =
diff --git a/chrome/browser/ash/accessibility/facegaze_test_utils.h b/chrome/browser/ash/accessibility/facegaze_test_utils.h
index 16822bd2..f564863 100644
--- a/chrome/browser/ash/accessibility/facegaze_test_utils.h
+++ b/chrome/browser/ash/accessibility/facegaze_test_utils.h
@@ -18,6 +18,14 @@
 // A class that can be used to exercise FaceGaze in browsertests.
 class FaceGazeTestUtils {
  public:
+  // A struct that holds cursor speed values.
+  struct CursorSpeeds {
+    int up;
+    int down;
+    int left;
+    int right;
+  };
+
   // A class that represents a fake FaceLandmarkerResult.
   class MockFaceLandmarkerResult {
    public:
@@ -27,13 +35,14 @@
     MockFaceLandmarkerResult& operator=(const MockFaceLandmarkerResult&) =
         delete;
 
-    void SetNormalizedForeheadLocation(double x, double y);
+    MockFaceLandmarkerResult& WithNormalizedForeheadLocation(double x,
+                                                             double y);
     const base::Value::Dict& forehead_location() const {
       return forehead_location_;
     }
 
-    void AddGestureWithConfidence(const std::string& gesture,
-                                  double confidence);
+    MockFaceLandmarkerResult& WithGesture(const std::string& gesture,
+                                          int confidence);
     const base::Value::List& recognized_gestures() const {
       return recognized_gestures_;
     }
@@ -52,16 +61,22 @@
   void EnableFaceGaze();
   // Creates and initializes the FaceLandmarker API within the extension.
   void CreateFaceLandmarker();
-  // Waits for the mouse location to propagate to the FaceGaze MouseController.
-  void WaitForMousePosition(const gfx::Point& location);
-  // Manually sets the buffer size on the FaceGaze MouseController.
-  void InitializeBufferSizeAndAcceleration(int size);
-  // Initializes the gesture to macro mapping.
-  void InitializeGesturesToMacros(const base::Value::Dict& gestures_to_macros);
+  // Waits for the cursor location to propagate to the FaceGaze MouseController.
+  void WaitForCursorPosition(const gfx::Point& location);
+  // Sets cursor speed prefs.
+  void SetCursorSpeeds(const CursorSpeeds& speeds);
+  // Sets the buffer size pref.
+  void SetBufferSize(int size);
+  // Sets the cursor acceleration pref.
+  void SetCursorAcceleration(bool use_acceleration);
+  // Sets the gesture to macro mapping pref.
+  void SetGesturesToMacros(const base::Value::Dict& gestures_to_macros);
+  // Sets the gesture confidences mapping pref.
+  void SetGestureConfidences(const base::Value::Dict& gesture_confidences);
   // Forces FaceGaze to process `result`, since tests don't have access to real
   // camera data.
   void ProcessFaceLandmarkerResult(const MockFaceLandmarkerResult& result);
-  // The MouseController updates the mouse location at a set interval. To
+  // The MouseController updates the cursor location at a set interval. To
   // increase test stability, the interval is canceled in tests, and must be
   // triggered manually using this method.
   void TriggerMouseControllerInterval();
diff --git a/chrome/browser/ash/accessibility/live_caption/DEPS b/chrome/browser/ash/accessibility/live_caption/DEPS
index cf6f662..88d35218 100644
--- a/chrome/browser/ash/accessibility/live_caption/DEPS
+++ b/chrome/browser/ash/accessibility/live_caption/DEPS
@@ -16,10 +16,10 @@
   # directory basis. See //tools/chromeos/gen_deps.sh for details.
   "+chrome/browser/accessibility/live_caption",
   "+chrome/browser/ash/login/session",
+  "+chrome/browser/browser_process.h",
   "+chrome/browser/profiles",
   "+chrome/browser/speech",
-  "+chrome/test/base",
-  "+chrome/browser/browser_process.h",
   "+chrome/browser/ui/browser.h",
   "+chrome/browser/ui/browser_window.h",
+  "+chrome/test/base",
 ]
diff --git a/chrome/browser/ash/accessibility/service/DEPS b/chrome/browser/ash/accessibility/service/DEPS
index 41553c1..bbbb6cb 100644
--- a/chrome/browser/ash/accessibility/service/DEPS
+++ b/chrome/browser/ash/accessibility/service/DEPS
@@ -21,10 +21,10 @@
   "+chrome/browser/speech",
   "+chrome/browser/ui/ash/keyboard",
   "+chrome/browser/ui/aura/accessibility",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/common/chrome_paths.h",
   "+chrome/common/extensions",
   "+chrome/test/base",
-  "+chrome/common/chrome_paths.h",
-  "+chrome/browser/ui/browser.h",
 
   # Dependencies outside of //chrome:
   "+services/accessibility/public/mojom",
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc b/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
index 6249878..2aabc1f 100644
--- a/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
+++ b/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
@@ -1025,7 +1025,7 @@
   sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
   sm_.ExpectSpeech("Images");
   sm_.ExpectSpeech("Checked");
-  sm_.ExpectSpeech("Image search by content and image previews");
+  sm_.ExpectSpeech("Search for text within images and see image previews");
 
   sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
   sm_.ExpectSpeech("Websites");
diff --git a/chrome/browser/ash/account_manager/DEPS b/chrome/browser/ash/account_manager/DEPS
new file mode 100644
index 0000000..d78ba31
--- /dev/null
+++ b/chrome/browser/ash/account_manager/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/account_manager",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/child_accounts",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/net",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/net",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/ash/edu_coexistence",
+  "+chrome/browser/ui/webui/signin/ash",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/android_sms/DEPS b/chrome/browser/ash/android_sms/DEPS
new file mode 100644
index 0000000..5bccbc3
--- /dev/null
+++ b/chrome/browser/ash/android_sms/DEPS
@@ -0,0 +1,35 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/android_sms",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/ash/multidevice_setup",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/content_settings",
+  "+chrome/browser/notifications",
+  "+chrome/browser/profiles",
+  "+chrome/browser/web_applications/external_install_options.h",
+  "+chrome/browser/web_applications/externally_managed_app_manager.h",
+  "+chrome/browser/web_applications/mojom",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_command_scheduler.h",
+  "+chrome/browser/web_applications/web_app_helpers.h",
+  "+chrome/browser/web_applications/web_app_install_finalizer.h",
+  "+chrome/browser/web_applications/web_app_provider_factory.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_utils.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/app_list/DEPS b/chrome/browser/ash/app_list/DEPS
index 1c60fc26..5164f76 100644
--- a/chrome/browser/ash/app_list/DEPS
+++ b/chrome/browser/ash/app_list/DEPS
@@ -1,4 +1,114 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_list",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/apps",
+  "+chrome/browser/ash/app_restore",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/borealis",
+  "+chrome/browser/ash/bruschetta",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/extensions",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/file_suggest",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/release_notes",
+  "+chrome/browser/ash/remote_apps",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/autocomplete",
+  "+chrome/browser/bitmap_fetcher",
+  "+chrome/browser/bookmarks",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/launcher_search",
+  "+chrome/browser/extensions/chrome_app_icon.h",
+  "+chrome/browser/extensions/context_menu_matcher.h",
+  "+chrome/browser/extensions/extension_browsertest.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/extension_service_test_base.h",
+  "+chrome/browser/extensions/extension_ui_util.h",
+  "+chrome/browser/extensions/extension_util.h",
+  "+chrome/browser/extensions/install_tracker_factory.h",
+  "+chrome/browser/extensions/install_tracker.h",
+  "+chrome/browser/extensions/launch_util.h",
+  "+chrome/browser/extensions/menu_manager_factory.h",
+  "+chrome/browser/extensions/menu_manager.h",
+  "+chrome/browser/extensions/pending_extension_manager.h",
+  "+chrome/browser/favicon",
+  "+chrome/browser/feature_engagement",
+  "+chrome/browser/history",
+  "+chrome/browser/image_decoder",
+  "+chrome/browser/metrics",
+  "+chrome/browser/notifications",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/resources/preinstalled_web_apps/internal",
+  "+chrome/browser/scalable_iph",
+  "+chrome/browser/screen_ai",
+  "+chrome/browser/search_engines",
+  "+chrome/browser/signin",
+  "+chrome/browser/sync",
+  "+chrome/browser/trusted_vault",
+  "+chrome/browser/ui/app_icon_loader_delegate.h",
+  "+chrome/browser/ui/app_icon_loader.h",
+  "+chrome/browser/ui/app_list",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_navigator.h",
+  "+chrome/browser/ui/browser_navigator_params.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/extensions",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/browser/ui/webui/ash/settings/app_management",
+  "+chrome/browser/ui/webui/ash/settings/calculator",
+  "+chrome/browser/ui/webui/ash/settings/pages/storage",
+  "+chrome/browser/ui/webui/ash/settings/search",
+  "+chrome/browser/ui/webui/ash/settings/services/settings_manager",
+  "+chrome/browser/ui/webui/ash/settings/test_support",
+  "+chrome/browser/ui/webui/chrome_web_ui_controller_factory.h",
+  "+chrome/browser/web_applications/externally_managed_app_manager.h",
+  "+chrome/browser/web_applications/mojom",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_command_manager.h",
+  "+chrome/browser/web_applications/web_app_icon_manager.h",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+ash/app_list/model",
   "+ash/resources",
   "+ash/strings",
diff --git a/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc b/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc
index c076f2a6..e6a9bbb 100644
--- a/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc
+++ b/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc
@@ -112,7 +112,7 @@
 namespace {
 
 const apps::PackageId kTestPackageId =
-    apps::PackageId(apps::AppType::kArc, "com.test.package");
+    apps::PackageId(apps::PackageType::kArc, "com.test.package");
 
 class TestObserver : public app_list::AppListSyncableService::Observer {
  public:
diff --git a/chrome/browser/ash/app_list/app_service/DEPS b/chrome/browser/ash/app_list/app_service/DEPS
index 8ce2515..c9df51e 100644
--- a/chrome/browser/ash/app_list/app_service/DEPS
+++ b/chrome/browser/ash/app_list/app_service/DEPS
@@ -1,3 +1,61 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_list/app_service",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps",
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/ash/app_restore",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/borealis",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/login/demo_mode",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/remote_apps",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/extensions/chrome_app_icon.h",
+  "+chrome/browser/extensions/context_menu_matcher.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/install_tracker_factory.h",
+  "+chrome/browser/extensions/install_tracker.h",
+  "+chrome/browser/extensions/menu_manager.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/app_icon_loader.h",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/browser/ui/webui/ash/settings/app_management",
+  "+chrome/browser/web_applications/mojom",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_command_manager.h",
+  "+chrome/browser/web_applications/web_app_icon_manager.h",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   "app_service_shortcut_item_browsertest.cc":
   ["+chrome/browser/ui/views/apps/app_dialog/shortcut_removal_dialog_view.h"]
diff --git a/chrome/browser/ash/app_list/app_service/app_service_promise_app_item_browsertest.cc b/chrome/browser/ash/app_list/app_service/app_service_promise_app_item_browsertest.cc
index 09b4a31..eb4e650 100644
--- a/chrome/browser/ash/app_list/app_service/app_service_promise_app_item_browsertest.cc
+++ b/chrome/browser/ash/app_list/app_service/app_service_promise_app_item_browsertest.cc
@@ -67,7 +67,7 @@
 namespace apps {
 
 const apps::PackageId kTestPackageId =
-    apps::PackageId(apps::AppType::kArc, "com.test.package");
+    apps::PackageId(apps::PackageType::kArc, "com.test.package");
 
 ash::AppListItem* GetAppListItem(const std::string& id) {
   return ash::AppListModelProvider::Get()->model()->FindItem(id);
@@ -366,7 +366,7 @@
                        CompleteAppInstallationRemovesPromiseAppItem) {
   AppType app_type = AppType::kArc;
   std::string identifier = "test.com.example";
-  PackageId package_id(app_type, identifier);
+  PackageId package_id(PackageType::kArc, identifier);
 
   // Register a promise app in the promise app registry cache.
   apps::PromiseAppPtr promise_app = std::make_unique<PromiseApp>(package_id);
@@ -683,9 +683,8 @@
 
 IN_PROC_BROWSER_TEST_F(AppServicePromiseAppItemBrowserTest,
                        InstalledAppPinnedWhenPinningPromiseApp) {
-  AppType app_type = AppType::kArc;
   std::string identifier = "test.com.example";
-  PackageId package_id(app_type, identifier);
+  PackageId package_id(PackageType::kArc, identifier);
 
   // Register a promise app in the promise app registry cache.
   apps::PromiseAppPtr promise_app = std::make_unique<PromiseApp>(package_id);
@@ -1053,7 +1052,7 @@
   const std::string app_name = "TestApp";
   const std::string activity_name = "TestActivity";
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, package_name);
+      apps::PackageId(apps::PackageType::kArc, package_name);
   const std::string app_id =
       ArcAppListPrefs::GetAppId(package_name, activity_name);
 
@@ -1143,7 +1142,7 @@
   const std::string app_name = "TestApp";
   const std::string activity_name = "TestActivity";
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, package_name);
+      apps::PackageId(apps::PackageType::kArc, package_name);
 
   // Skip check for official API key.
   app_service_proxy()->PromiseAppService()->SetSkipApiKeyCheckForTesting(true);
@@ -1175,7 +1174,7 @@
   const std::string app_name = "TestApp";
   const std::string activity_name = "TestActivity";
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, package_name);
+      apps::PackageId(apps::PackageType::kArc, package_name);
 
   // Skip check for official API key.
   app_service_proxy()->PromiseAppService()->SetSkipApiKeyCheckForTesting(true);
@@ -1214,7 +1213,7 @@
   const std::string app_name = "TestApp";
   const std::string activity_name = "TestActivity";
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, package_name);
+      apps::PackageId(apps::PackageType::kArc, package_name);
 
   // Skip check for official API key.
   app_service_proxy()->PromiseAppService()->SetSkipApiKeyCheckForTesting(true);
@@ -1250,7 +1249,7 @@
   const std::string app_name = "TestApp";
   const std::string activity_name = "TestActivity";
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, package_name);
+      apps::PackageId(apps::PackageType::kArc, package_name);
 
   // Skip check for official API key.
   app_service_proxy()->PromiseAppService()->SetSkipApiKeyCheckForTesting(true);
diff --git a/chrome/browser/ash/app_list/app_service/app_service_promise_app_model_builder_unittest.cc b/chrome/browser/ash/app_list/app_service/app_service_promise_app_model_builder_unittest.cc
index e82a9a7..c7d4c96a 100644
--- a/chrome/browser/ash/app_list/app_service/app_service_promise_app_model_builder_unittest.cc
+++ b/chrome/browser/ash/app_list/app_service/app_service_promise_app_model_builder_unittest.cc
@@ -80,12 +80,12 @@
   void RegisterTestApps() {
     // Register two promise apps in the promise app registry cache.
     apps::PromiseAppPtr promise_app_1 = std::make_unique<apps::PromiseApp>(
-        apps::PackageId(apps::AppType::kArc, "test1"));
+        apps::PackageId(apps::PackageType::kArc, "test1"));
     promise_app_1->should_show = true;
     cache()->OnPromiseApp(std::move(promise_app_1));
 
     apps::PromiseAppPtr promise_app_2 = std::make_unique<apps::PromiseApp>(
-        apps::PackageId(apps::AppType::kArc, "test2"));
+        apps::PackageId(apps::PackageType::kArc, "test2"));
     promise_app_2->should_show = true;
     cache()->OnPromiseApp(std::move(promise_app_2));
   }
diff --git a/chrome/browser/ash/app_list/arc/DEPS b/chrome/browser/ash/app_list/arc/DEPS
index b118d9f..77ca8953 100644
--- a/chrome/browser/ash/app_list/arc/DEPS
+++ b/chrome/browser/ash/app_list/arc/DEPS
@@ -1,3 +1,42 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_list/arc",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/extension_service_test_base.h",
+  "+chrome/browser/image_decoder",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sync",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/web_applications/test",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   "arc_app_list_prefs\.cc": [
      "+ash",
diff --git a/chrome/browser/ash/app_list/chat/DEPS b/chrome/browser/ash/app_list/chat/DEPS
new file mode 100644
index 0000000..dec911f
--- /dev/null
+++ b/chrome/browser/ash/app_list/chat/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_list/chat",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_list/search",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/app_list/internal_app/DEPS b/chrome/browser/ash/app_list/internal_app/DEPS
new file mode 100644
index 0000000..39a00e8
--- /dev/null
+++ b/chrome/browser/ash/app_list/internal_app/DEPS
@@ -0,0 +1,26 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_list/internal_app",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service/metrics",
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/release_notes",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/browser/ui/extensions",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/grit",
+]
diff --git a/chrome/browser/ash/app_list/reorder/DEPS b/chrome/browser/ash/app_list/reorder/DEPS
new file mode 100644
index 0000000..a56cf98
--- /dev/null
+++ b/chrome/browser/ash/app_list/reorder/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_list/reorder",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/ui/ash",
+]
diff --git a/chrome/browser/ash/app_list/search/DEPS b/chrome/browser/ash/app_list/search/DEPS
index 31e6f0d..e629860 100644
--- a/chrome/browser/ash/app_list/search/DEPS
+++ b/chrome/browser/ash/app_list/search/DEPS
@@ -1,4 +1,73 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_list/search",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_discovery_service",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/extensions",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/file_suggest",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/release_notes",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/autocomplete",
+  "+chrome/browser/bitmap_fetcher",
+  "+chrome/browser/bookmarks",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/launcher_search",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/favicon",
+  "+chrome/browser/history",
+  "+chrome/browser/metrics",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/screen_ai",
+  "+chrome/browser/search_engines",
+  "+chrome/browser/signin",
+  "+chrome/browser/sync",
+  "+chrome/browser/trusted_vault",
+  "+chrome/browser/ui/app_icon_loader_delegate.h",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/ash/settings/calculator",
+  "+chrome/browser/ui/webui/ash/settings/pages/storage",
+  "+chrome/browser/ui/webui/ash/settings/search",
+  "+chrome/browser/ui/webui/ash/settings/services/settings_manager",
+  "+chrome/browser/ui/webui/ash/settings/test_support",
+  "+chrome/browser/ui/webui/chrome_web_ui_controller_factory.h",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+ash/accelerators",
   "+ash/assistant/model",
   "+ash/assistant/util",
diff --git a/chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider_unittest.cc b/chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider_unittest.cc
index 72a1d41d..60e0e65 100644
--- a/chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "ash/constants/ash_features.h"
@@ -138,8 +139,7 @@
 
 }  // namespace
 
-// Our main test fixture. Provides `search_producer_` with which tests can
-// produce "lacros-side" results, and `search_controller_` with which tests can
+// Our main test fixture. Provides `search_controller_` with which tests can
 // read the output of the `OmniboxLacrosProvider`.
 class OmniboxLacrosProviderTest : public testing::Test {
  public:
@@ -173,12 +173,6 @@
 
     crosapi_manager_ = crosapi::CreateCrosapiManagerWithTestRegistry();
 
-    // Create fake lacros-side logic.
-    search_producer_ = std::make_unique<TestSearchResultProducer>();
-    crosapi_manager_->crosapi_ash()
-        ->search_provider_ash()
-        ->RegisterSearchController(search_producer_->BindToRemote());
-
     // Create client of our provider.
     search_controller_ = std::make_unique<TestSearchController>();
 
@@ -192,18 +186,17 @@
   void TearDown() override {
     omnibox_provider_ = nullptr;
     search_controller_.reset();
-    search_producer_.reset();
     crosapi_manager_.reset();
     ash::LoginState::Shutdown();
     profile_ = nullptr;
     profile_manager_->DeleteTestingProfile(chrome::kInitialProfile);
   }
 
-  // Tells the producer to produce the given results, then waits for the results
-  // to be transmitted over their Mojo pipe.
-  void ProduceResults(std::vector<cam::SearchResultPtr> results) {
-    search_producer_->ProduceResults(std::move(results));
-    base::RunLoop().RunUntilIdle();
+  void RegisterSearchController(
+      mojo::PendingRemote<cam::SearchController> search_controller) {
+    crosapi_manager_->crosapi_ash()
+        ->search_provider_ash()
+        ->RegisterSearchController(std::move(search_controller));
   }
 
   // Starts a search and waits for the query to be sent to "lacros" over a Mojo
@@ -222,7 +215,6 @@
   }
 
  protected:
-  std::unique_ptr<TestSearchResultProducer> search_producer_;
   std::unique_ptr<TestSearchController> search_controller_;
 
  private:
@@ -239,15 +231,18 @@
 
 // Test that results sent from lacros each instantiate a Chrome search result.
 TEST_F(OmniboxLacrosProviderTest, Basic) {
+  auto search_producer = std::make_unique<TestSearchResultProducer>();
+  RegisterSearchController(search_producer->BindToRemote());
   StartSearch(u"query");
-  EXPECT_EQ(u"query", search_producer_->last_query());
+  EXPECT_EQ(u"query", search_producer->last_query());
 
   std::vector<cam::SearchResultPtr> to_produce;
   to_produce.emplace_back(NewOmniboxResult("https://example.com/result"));
   to_produce.emplace_back(NewAnswerResult(
       "https://example.com/answer", cam::SearchResult::AnswerType::kWeather));
   to_produce.emplace_back(NewOpenTabResult("https://example.com/open_tab"));
-  ProduceResults(std::move(to_produce));
+  search_producer->ProduceResults(std::move(to_produce));
+  base::RunLoop().RunUntilIdle();
 
   // Results always appear after answer and open tab entries.
   ASSERT_EQ(3u, search_controller_->last_results().size());
@@ -261,19 +256,23 @@
 
 // Test that newly-produced results supersede previous results.
 TEST_F(OmniboxLacrosProviderTest, NewResults) {
+  auto search_producer = std::make_unique<TestSearchResultProducer>();
+  RegisterSearchController(search_producer->BindToRemote());
   StartSearch(u"query");
 
   // Produce one result.
   std::vector<cam::SearchResultPtr> to_produce;
   to_produce.emplace_back(NewOpenTabResult("https://example.com/open_tab_1"));
-  ProduceResults(std::move(to_produce));
+  search_producer->ProduceResults(std::move(to_produce));
+  base::RunLoop().RunUntilIdle();
 
   StartSearch(u"query2");
 
   // Then produce another.
   to_produce.clear();
   to_produce.emplace_back(NewOpenTabResult("https://example.com/open_tab_2"));
-  ProduceResults(std::move(to_produce));
+  search_producer->ProduceResults(std::move(to_produce));
+  base::RunLoop().RunUntilIdle();
 
   // Only newest result should be stored.
   ASSERT_EQ(1u, search_controller_->last_results().size());
@@ -283,6 +282,8 @@
 
 // Test that invalid URLs aren't accepted.
 TEST_F(OmniboxLacrosProviderTest, BadUrls) {
+  auto search_producer = std::make_unique<TestSearchResultProducer>();
+  RegisterSearchController(search_producer->BindToRemote());
   StartSearch(u"query");
 
   // All results have bad URLs.
@@ -291,7 +292,8 @@
   to_produce.emplace_back(
       NewAnswerResult("badscheme", cam::SearchResult::AnswerType::kWeather));
   to_produce.emplace_back(NewOpenTabResult("http://?k=v"));
-  ProduceResults(std::move(to_produce));
+  search_producer->ProduceResults(std::move(to_produce));
+  base::RunLoop().RunUntilIdle();
 
   // None of the results should be accepted.
   EXPECT_TRUE(search_controller_->last_results().empty());
@@ -299,6 +301,8 @@
 
 // Test that results with the same URL are deduplicated in the correct order.
 TEST_F(OmniboxLacrosProviderTest, Deduplicate) {
+  auto search_producer = std::make_unique<TestSearchResultProducer>();
+  RegisterSearchController(search_producer->BindToRemote());
   StartSearch(u"query");
 
   // A result that has the same URL as another result, but is a history (i.e.
@@ -314,7 +318,8 @@
   to_produce.emplace_back(NewOmniboxResult("https://example.com/result_1"));
   to_produce.emplace_back(std::move(history_result));
 
-  ProduceResults(std::move(to_produce));
+  search_producer->ProduceResults(std::move(to_produce));
+  base::RunLoop().RunUntilIdle();
 
   // Only the higher-priority (i.e. history) result for URL 1 should be kept.
   ASSERT_EQ(2u, search_controller_->last_results().size());
@@ -328,6 +333,8 @@
 // Test that results aren't created for URLs for which there are other
 // specialist producers.
 TEST_F(OmniboxLacrosProviderTest, UnhandledUrls) {
+  auto search_producer = std::make_unique<TestSearchResultProducer>();
+  RegisterSearchController(search_producer->BindToRemote());
   StartSearch(u"query");
 
   // Drive URLs aren't handled (_unless_ they are open tabs pointing to the
@@ -339,7 +346,8 @@
   to_produce.emplace_back(NewOpenTabResult("https://drive.google.com/doc1"));
   to_produce.emplace_back(NewOpenTabResult("https://docs.google.com/doc2"));
   to_produce.emplace_back(NewOpenTabResult("file:///docs/doc3"));
-  ProduceResults(std::move(to_produce));
+  search_producer->ProduceResults(std::move(to_produce));
+  base::RunLoop().RunUntilIdle();
 
   ASSERT_EQ(2u, search_controller_->last_results().size());
   EXPECT_EQ("opentab://https://drive.google.com/doc1",
@@ -351,20 +359,23 @@
 // Test that all non-answer results are filtered if web search is disabled in
 // search control.
 TEST_F(OmniboxLacrosProviderTest, WebSearchControl) {
+  auto search_producer = std::make_unique<TestSearchResultProducer>();
+  RegisterSearchController(search_producer->BindToRemote());
   base::test::ScopedFeatureList scoped_feature_list_;
   scoped_feature_list_.InitAndEnableFeature(
       ash::features::kLauncherSearchControl);
   DisableWebSearch();
 
   StartSearch(u"query");
-  EXPECT_EQ(u"query", search_producer_->last_query());
+  EXPECT_EQ(u"query", search_producer->last_query());
 
   std::vector<cam::SearchResultPtr> to_produce;
   to_produce.emplace_back(NewOmniboxResult("https://example.com/result"));
   to_produce.emplace_back(NewAnswerResult(
       "https://example.com/answer", cam::SearchResult::AnswerType::kWeather));
   to_produce.emplace_back(NewOpenTabResult("https://example.com/open_tab"));
-  ProduceResults(std::move(to_produce));
+  search_producer->ProduceResults(std::move(to_produce));
+  base::RunLoop().RunUntilIdle();
 
   // Results always appear after answer and open tab entries.
   ASSERT_EQ(1u, search_controller_->last_results().size());
@@ -372,78 +383,7 @@
             search_controller_->last_results()[0]->id());
 }
 
-// A secondary test fixture which does not have any search producer connected to
-// the crosapi manager.
-class OmniboxLacrosProviderNoCrosAPITest : public testing::Test {
- public:
-  OmniboxLacrosProviderNoCrosAPITest() = default;
-  OmniboxLacrosProviderNoCrosAPITest(
-      const OmniboxLacrosProviderNoCrosAPITest&) = delete;
-  OmniboxLacrosProviderNoCrosAPITest& operator=(
-      const OmniboxLacrosProviderNoCrosAPITest&) = delete;
-  ~OmniboxLacrosProviderNoCrosAPITest() override = default;
-
-  void SetUp() override {
-    // Create the profile manager and an active profile.
-    profile_manager_ = std::make_unique<TestingProfileManager>(
-        TestingBrowserProcess::GetGlobal());
-    ASSERT_TRUE(profile_manager_->SetUp());
-    // The profile needs a template URL service for history Omnibox results.
-    profile_ = profile_manager_->CreateTestingProfile(
-        chrome::kInitialProfile,
-        {{TemplateURLServiceFactory::GetInstance(),
-          base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor)}});
-
-    // The idle service has dependencies we can't instantiate in unit tests.
-    crosapi::IdleServiceAsh::DisableForTesting();
-
-    // The crosapi manager reads the global login state.
-    ash::LoginState::Initialize();
-
-    crosapi_manager_ = crosapi::CreateCrosapiManagerWithTestRegistry();
-
-    // Create fake lacros-side logic.
-    crosapi_manager_->crosapi_ash()->search_provider_ash();
-
-    // Create client of our provider.
-    search_controller_ = std::make_unique<TestSearchController>();
-
-    // Create the object to actually test.
-    auto omnibox_provider = std::make_unique<OmniboxLacrosProvider>(
-        profile_, &list_controller_, crosapi_manager_.get());
-    omnibox_provider_ = omnibox_provider.get();
-    search_controller_->AddProvider(std::move(omnibox_provider));
-  }
-
-  void TearDown() override {
-    omnibox_provider_ = nullptr;
-    search_controller_.reset();
-    crosapi_manager_.reset();
-    ash::LoginState::Shutdown();
-    profile_ = nullptr;
-    profile_manager_->DeleteTestingProfile(chrome::kInitialProfile);
-  }
-
-  // Starts a search and waits for the query to be sent to "lacros" over a Mojo
-  // pipe.
-  void StartSearch(const std::u16string& query) {
-    search_controller_->StartSearch(query);
-    base::RunLoop().RunUntilIdle();
-  }
-
- protected:
-  std::unique_ptr<TestSearchController> search_controller_;
-
- private:
-  content::BrowserTaskEnvironment task_environment_;
-  TestAppListControllerDelegate list_controller_;
-  std::unique_ptr<crosapi::CrosapiManager> crosapi_manager_;
-  std::unique_ptr<TestingProfileManager> profile_manager_;
-  raw_ptr<TestingProfile> profile_;
-  raw_ptr<OmniboxLacrosProvider> omnibox_provider_;
-};
-
-TEST_F(OmniboxLacrosProviderNoCrosAPITest, SystemURLsWorkWithNoSearchProvider) {
+TEST_F(OmniboxLacrosProviderTest, SystemURLsWorkWithNoSearchProvider) {
   StartSearch(u"os://flags");
 
   ASSERT_EQ(1u, search_controller_->last_results().size());
diff --git a/chrome/browser/ash/app_list/test/DEPS b/chrome/browser/ash/app_list/test/DEPS
new file mode 100644
index 0000000..aaa7a7e
--- /dev/null
+++ b/chrome/browser/ash/app_list/test/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_list/test",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/app_list/vector_icons/DEPS b/chrome/browser/ash/app_list/vector_icons/DEPS
new file mode 100644
index 0000000..22312d38
--- /dev/null
+++ b/chrome/browser/ash/app_list/vector_icons/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_list/vector_icons",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/app_mode/DEPS b/chrome/browser/ash/app_mode/DEPS
index 6cd63e0..6e3662f 100644
--- a/chrome/browser/ash/app_mode/DEPS
+++ b/chrome/browser/ash/app_mode/DEPS
@@ -1,3 +1,90 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_mode",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/app_mode",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/apps/platform_apps",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/arc/kiosk",
+  "+chrome/browser/ash/arc/policy",
+  "+chrome/browser/ash/arc/session",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/extensions",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/notifications",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/enrollment",
+  "+chrome/browser/ash/policy/scheduled_task_handler",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/ash/system",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chromeos/app_mode",
+  "+chrome/browser/devtools",
+  "+chrome/browser/extensions/cws_item_service.pb.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/extension_service_test_base.h",
+  "+chrome/browser/extensions/extension_special_storage_policy.h",
+  "+chrome/browser/extensions/external_loader.h",
+  "+chrome/browser/extensions/external_provider_impl.h",
+  "+chrome/browser/extensions/install_gate.h",
+  "+chrome/browser/extensions/install_tracker.h",
+  "+chrome/browser/extensions/pending_extension_manager.h",
+  "+chrome/browser/extensions/webstore_data_fetcher_delegate.h",
+  "+chrome/browser/extensions/webstore_data_fetcher.h",
+  "+chrome/browser/extensions/webstore_install_helper.h",
+  "+chrome/browser/image_decoder",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/net",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/apps",
+  "+chrome/browser/ui/ash/multi_user",
+  "+chrome/browser/ui/ash/system_web_apps",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chromeos",
+  "+chrome/browser/ui/views/frame",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/browser/web_applications/external_install_options.h",
+  "+chrome/browser/web_applications/externally_managed_app_manager.h",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_constants.h",
+  "+chrome/browser/web_applications/web_app_helpers.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_registrar.h",
+  "+chrome/browser/web_applications/web_app_registry_update.h",
+  "+chrome/browser/web_applications/web_app_ui_manager.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/initialize_extensions_client.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   "kiosk_troubleshooting_tools_browsertest.cc": [
     "+chrome/browser/ui/views/task_manager_view.h",
diff --git a/chrome/browser/ash/app_mode/arc/DEPS b/chrome/browser/ash/app_mode/arc/DEPS
new file mode 100644
index 0000000..bce1ba7
--- /dev/null
+++ b/chrome/browser/ash/app_mode/arc/DEPS
@@ -0,0 +1,32 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_mode/arc",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/app_mode",
+  "+chrome/browser/ash/arc/kiosk",
+  "+chrome/browser/ash/arc/policy",
+  "+chrome/browser/ash/arc/session",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/multi_user",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/chromeos",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/app_mode/auto_sleep/DEPS b/chrome/browser/ash/app_mode/auto_sleep/DEPS
new file mode 100644
index 0000000..6306809
--- /dev/null
+++ b/chrome/browser/ash/app_mode/auto_sleep/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_mode/auto_sleep",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_mode/web_app",
+  "+chrome/browser/ash/login/app_mode/test",
+  "+chrome/browser/ash/policy/scheduled_task_handler",
+  "+chrome/browser/browser_process.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/app_mode/metrics/DEPS b/chrome/browser/ash/app_mode/metrics/DEPS
index 00643e08..2a7c844 100644
--- a/chrome/browser/ash/app_mode/metrics/DEPS
+++ b/chrome/browser/ash/app_mode/metrics/DEPS
@@ -1,3 +1,23 @@
 include_rules = [
-    "+services/preferences/public/cpp",
-]
\ No newline at end of file
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_mode/metrics",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/browser_process.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
+  "+services/preferences/public/cpp",
+]
diff --git a/chrome/browser/ash/app_mode/web_app/DEPS b/chrome/browser/ash/app_mode/web_app/DEPS
new file mode 100644
index 0000000..cfa46824
--- /dev/null
+++ b/chrome/browser/ash/app_mode/web_app/DEPS
@@ -0,0 +1,41 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_mode/web_app",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_mode",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/app_mode",
+  "+chrome/browser/extensions/extension_special_storage_policy.h",
+  "+chrome/browser/image_decoder",
+  "+chrome/browser/net",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/browser/web_applications/external_install_options.h",
+  "+chrome/browser/web_applications/externally_managed_app_manager.h",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_constants.h",
+  "+chrome/browser/web_applications/web_app_helpers.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_registrar.h",
+  "+chrome/browser/web_applications/web_app_registry_update.h",
+  "+chrome/browser/web_applications/web_app_ui_manager.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/app_restore/DEPS b/chrome/browser/ash/app_restore/DEPS
new file mode 100644
index 0000000..54f64033
--- /dev/null
+++ b/chrome/browser/ash/app_restore/DEPS
@@ -0,0 +1,53 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/app_restore",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/app_mode",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/apps/platform_apps",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chromeos/full_restore",
+  "+chrome/browser/first_run",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sessions",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_tabstrip.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/startup",
+  "+chrome/browser/ui/web_applications/test",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_utils.h",
+  "+chrome/common/buildflags.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+  "+chrome/test/views",
+]
diff --git a/chrome/browser/ash/apps/DEPS b/chrome/browser/ash/apps/DEPS
new file mode 100644
index 0000000..ef7dce7
--- /dev/null
+++ b/chrome/browser/ash/apps/DEPS
@@ -0,0 +1,42 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/apps",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/web_applications/test",
+  "+chrome/browser/web_applications/mojom",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_command_scheduler.h",
+  "+chrome/browser/web_applications/web_app_constants.h",
+  "+chrome/browser/web_applications/web_app.h",
+  "+chrome/browser/web_applications/web_app_helpers.h",
+  "+chrome/browser/web_applications/web_app_icon_generator.h",
+  "+chrome/browser/web_applications/web_app_install_finalizer.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_install_params.h",
+  "+chrome/browser/web_applications/web_app_install_utils.h",
+  "+chrome/browser/web_applications/web_app_provider_factory.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_registrar.h",
+  "+chrome/browser/web_applications/web_app_utils.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/DEPS b/chrome/browser/ash/arc/DEPS
index f50e1bc..00b8418 100644
--- a/chrome/browser/ash/arc/DEPS
+++ b/chrome/browser/ash/arc/DEPS
@@ -1,4 +1,84 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/chromeos/arc",
+  "+chrome/browser/chromeos/platform_keys",
+  "+chrome/browser/chromeos/policy/dlp",
+  "+chrome/browser/consent_auditor",
+  "+chrome/browser/extensions/api/settings_private",
+  "+chrome/browser/extensions/api/tabs",
+  "+chrome/browser/extensions/extension_tab_util.h",
+  "+chrome/browser/image_decoder",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/media/webrtc",
+  "+chrome/browser/memory",
+  "+chrome/browser/metrics",
+  "+chrome/browser/nearby_sharing",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/pdf",
+  "+chrome/browser/picture_in_picture",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/printing",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sessions",
+  "+chrome/browser/sharesheet",
+  "+chrome/browser/signin",
+  "+chrome/browser/speech",
+  "+chrome/browser/sync",
+  "+chrome/browser/tab_contents",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_list_observer.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/chrome_select_file_policy.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/simple_message_box.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/test",
+  "+chrome/browser/ui/webui/ash",
+  "+chrome/browser/ui/webui/signin/ash",
+  "+chrome/browser/ui/zoom",
+  "+chrome/browser/webshare",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_isolated_world_ids.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_paths_internal.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions/api",
+  "+chrome/common/net",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/services/printing/public/mojom",
+  "+chrome/test/base",
+  "+chrome/test/views",
+
+  # Dependencies outside of //chrome:
   "+chrome/services/keymanagement/public",
   "+chrome/services/keymaster/public",
   "+chrome/services/keymint/public",
diff --git a/chrome/browser/ash/arc/accessibility/DEPS b/chrome/browser/ash/arc/accessibility/DEPS
index 20911a6..870e2e2 100644
--- a/chrome/browser/ash/arc/accessibility/DEPS
+++ b/chrome/browser/ash/arc/accessibility/DEPS
@@ -1,3 +1,30 @@
 include_rules = [
-    "+services/accessibility/android",
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/accessibility",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/accessibility",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/arc/input_method_manager",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/shelf/app_service",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/common/extensions/api",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+  "+chrome/test/views",
+
+  # Dependencies outside of //chrome:
+  "+services/accessibility/android",
 ]
diff --git a/chrome/browser/ash/arc/adbd/DEPS b/chrome/browser/ash/arc/adbd/DEPS
new file mode 100644
index 0000000..4a17ed4
--- /dev/null
+++ b/chrome/browser/ash/arc/adbd/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/adbd",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc/session",
+  "+chrome/browser/ash/arc/test",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/app_shortcuts/DEPS b/chrome/browser/ash/arc/app_shortcuts/DEPS
new file mode 100644
index 0000000..1a2244f
--- /dev/null
+++ b/chrome/browser/ash/arc/app_shortcuts/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/app_shortcuts",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/auth/DEPS b/chrome/browser/ash/arc/auth/DEPS
new file mode 100644
index 0000000..af9e691
--- /dev/null
+++ b/chrome/browser/ash/arc/auth/DEPS
@@ -0,0 +1,39 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/auth",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/account_manager",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/login/demo_mode",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/net",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/ash/multi_user",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/signin/ash",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/bluetooth/DEPS b/chrome/browser/ash/arc/bluetooth/DEPS
new file mode 100644
index 0000000..9dfb7db1
--- /dev/null
+++ b/chrome/browser/ash/arc/bluetooth/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/bluetooth",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/webui/ash",
+]
diff --git a/chrome/browser/ash/arc/boot_phase_monitor/DEPS b/chrome/browser/ash/arc/boot_phase_monitor/DEPS
new file mode 100644
index 0000000..9227064
--- /dev/null
+++ b/chrome/browser/ash/arc/boot_phase_monitor/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/boot_phase_monitor",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sessions",
+  "+chrome/browser/ui/ash/multi_user",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/enterprise/DEPS b/chrome/browser/ash/arc/enterprise/DEPS
new file mode 100644
index 0000000..e022387
--- /dev/null
+++ b/chrome/browser/ash/arc/enterprise/DEPS
@@ -0,0 +1,35 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/enterprise",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/platform_keys",
+  "+chrome/browser/ash/policy/affiliation",
+  "+chrome/browser/ash/policy/remote_commands",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/chromeos/platform_keys",
+  "+chrome/browser/net",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/common/net",
+  "+chrome/common/pref_names.h",
+  "+chrome/services/keymanagement/public/mojom",
+  "+chrome/services/keymaster/public/mojom",
+  "+chrome/services/keymint/public/mojom",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/extensions/DEPS b/chrome/browser/ash/arc/extensions/DEPS
new file mode 100644
index 0000000..c52f50e6
--- /dev/null
+++ b/chrome/browser/ash/arc/extensions/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/extensions",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/arc/file_system_watcher/DEPS b/chrome/browser/ash/arc/file_system_watcher/DEPS
new file mode 100644
index 0000000..04cb7365
--- /dev/null
+++ b/chrome/browser/ash/arc/file_system_watcher/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/file_system_watcher",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc/fileapi",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/profiles",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/fileapi/DEPS b/chrome/browser/ash/arc/fileapi/DEPS
index b5b5efc..ec9ed33 100644
--- a/chrome/browser/ash/arc/fileapi/DEPS
+++ b/chrome/browser/ash/arc/fileapi/DEPS
@@ -1,4 +1,33 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/fileapi",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/file_system_provider",
+  "+chrome/browser/ash/policy/dlp",
+  "+chrome/browser/chromeos/policy/dlp",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/browser/ui/chrome_select_file_policy.h",
+  "+chrome/common/chrome_isolated_world_ids.h",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   # For ArcSelectFilesHandler.
   "+chrome/browser/ui/views/select_file_dialog_extension.h",
 ]
diff --git a/chrome/browser/ash/arc/idle_manager/DEPS b/chrome/browser/ash/arc/idle_manager/DEPS
new file mode 100644
index 0000000..16dd2ceb
--- /dev/null
+++ b/chrome/browser/ash/arc/idle_manager/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/idle_manager",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/input_method_manager/DEPS b/chrome/browser/ash/arc/input_method_manager/DEPS
new file mode 100644
index 0000000..4d9afc4d
--- /dev/null
+++ b/chrome/browser/ash/arc/input_method_manager/DEPS
@@ -0,0 +1,24 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/input_method_manager",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/accessibility",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/input_method",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/keyboard",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/input_overlay/DEPS b/chrome/browser/ash/arc/input_overlay/DEPS
new file mode 100644
index 0000000..4231dbc
--- /dev/null
+++ b/chrome/browser/ash/arc/input_overlay/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/input_overlay",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/profiles",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/input_overlay/constants.h b/chrome/browser/ash/arc/input_overlay/constants.h
index a6a21707..7d4900c6 100644
--- a/chrome/browser/ash/arc/input_overlay/constants.h
+++ b/chrome/browser/ash/arc/input_overlay/constants.h
@@ -26,6 +26,9 @@
 // Total key size for ActionMoveKey.
 constexpr size_t kActionMoveKeysSize = 4;
 
+// Maximum of actions size.
+inline constexpr size_t kMaxActionCount = 50;
+
 constexpr char16_t kUnknownBind[] = u"?";
 
 // Directions from up, left, down, right.
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
index a8b3503..7e12de7 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
@@ -121,7 +121,7 @@
     // Once there is next focusable view (dont_loop==true), it means the current
     // focus is not the first or the last focusable view, so it doesn't need to
     // change focus to the next widget.
-    if (auto* next_focus = focus_manager->GetNextFocusableView(
+    if (focus_manager->GetNextFocusableView(
             /*starting_view=*/focus_manager->GetFocusedView(),
             /*starting_widget=*/target_widget, /*reverse=*/reverse,
             /*dont_loop=*/true)) {
@@ -1401,7 +1401,7 @@
 }
 
 void DisplayOverlayController::AddRichNudge() {
-  if (auto* target_view = GetTargetView()) {
+  if (GetTargetView()) {
     rich_nudge_widget_ = views::BubbleDialogDelegateView::CreateBubble(
         std::make_unique<RichNudge>(target_widget_->GetNativeWindow()));
     rich_nudge_widget_->ShowInactive();
diff --git a/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.cc b/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.cc
index fce0fd7..051b395 100644
--- a/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.cc
+++ b/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.cc
@@ -44,6 +44,13 @@
   LeftClickOn(editing_list_->GetAddButtonForTesting());
 }
 
+void OverlayViewTestBase::PressAddContainerButton() {
+  if (!editing_list_) {
+    return;
+  }
+  LeftClickOn(editing_list_->GetAddContainerButtonForTesting());
+}
+
 void OverlayViewTestBase::AddNewActionInCenter() {
   DCHECK(editing_list_);
 
diff --git a/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h b/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h
index 881ae77..cc5b0a3 100644
--- a/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h
+++ b/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h
@@ -31,6 +31,7 @@
  protected:
   void EnableEditMode();
   void PressAddButton();
+  void PressAddContainerButton();
 
   // Adds a new action in the center of the main window.
   void AddNewActionInCenter();
diff --git a/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.cc b/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.cc
index 817a4d4..24c826825 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.cc
@@ -22,6 +22,7 @@
 #include "ui/gfx/geometry/insets.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/bubble/bubble_border.h"
+#include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/view_class_properties.h"
@@ -50,7 +51,20 @@
   set_internal_name(kDeleteEditShortcut);
   set_parent_window(anchor_view->GetWidget()->GetNativeWindow());
   SetButtons(ui::DIALOG_BUTTON_NONE);
-  SetAccessibleWindowRole(ax::mojom::Role::kMenu);
+  SetEnableArrowKeyTraversal(true);
+
+  // BubbleDialogDelegate::GetAccessibleWindowRole() is a final method which
+  // can't override. If the window role is `kWindow`, it will force set it to
+  // alert dialog and reads all the tooltips inside.
+  // SetAccessibleWindowRole(kDialog) can prevent it.
+  // SetAccessibleWindowRole(ax::mojom::Role::kMenu) results in screenreader to
+  // announce the menu having only one item.
+  SetAccessibleWindowRole(ax::mojom::Role::kDialog);
+
+  // Set root view to menu.
+  GetViewAccessibility().SetRole(ax::mojom::Role::kMenu);
+  SetAccessibleTitle(
+      l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_SHORTCUT_MENU_A11Y_LABEL));
 
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical,
@@ -64,12 +78,15 @@
                           base::Unretained(this)),
       ash::IconButton::Type::kMedium, &kGameControlsEditPenIcon, u"",
       /*is_togglable=*/false, /*has_border=*/false));
+  edit_button_->GetViewAccessibility().SetRole(ax::mojom::Role::kMenuItem);
 
   delete_button_ = AddChildView(std::make_unique<ash::IconButton>(
       base::BindRepeating(&DeleteEditShortcut::OnDeleteButtonPressed,
                           base::Unretained(this)),
       ash::IconButton::Type::kMedium, &kGameControlsDeleteIcon, u"",
       /*is_togglable=*/false, /*has_border=*/false));
+  delete_button_->GetViewAccessibility().SetRole(ax::mojom::Role::kMenuItem);
+
   UpdateTooltipText(anchor_view);
 }
 
diff --git a/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc b/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc
index cc4d4d9..314119c 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc
@@ -76,8 +76,6 @@
 // will not be cut for the top and bottom list item.
 constexpr int kAddRowBottomMargin = 8 - kSpaceForFocusRing;
 
-constexpr size_t kMaxActionCount = 50;
-
 constexpr char kKeyEditNudgeID[] = "kGameControlsKeyEditNudge";
 constexpr char kHelpUrl[] =
     "https://support.google.com/chromebook/?p=game-controls-help";
@@ -183,7 +181,16 @@
   }
 
   void UpdateAddButtonState(size_t current_controls_size) {
-    add_button_->SetEnabled(current_controls_size < kMaxActionCount);
+    const ButtonState state = current_controls_size < kMaxActionCount
+                                  ? ButtonState::STATE_NORMAL
+                                  : ButtonState::STATE_DISABLED;
+    if (state == GetState()) {
+      return;
+    }
+
+    add_button_->SetState(state);
+    SetState(state);
+    // TODO(b/333583970): Update the colors.
   }
 
   views::LabelButton* add_button() { return add_button_; }
@@ -241,6 +248,7 @@
   add_container_ =
       AddChildView(std::make_unique<AddContainerButton>(base::BindRepeating(
           &EditingList::OnAddButtonPressed, base::Unretained(this))));
+  add_container_->UpdateAddButtonState(controller_->GetActiveActionsSize());
 
   scroll_view_ = AddChildView(std::make_unique<views::ScrollView>());
   scroll_view_->SetBackgroundColor(std::nullopt);
@@ -584,7 +592,8 @@
 
 void EditingList::OnActionAdded(Action& action) {
   DCHECK(scroll_content_);
-  if (controller_->GetActiveActionsSize() == 1u) {
+  const size_t active_action_size = controller_->GetActiveActionsSize();
+  if (active_action_size == 1u) {
     // Clear the zero-state.
     UpdateOnZeroState(/*is_zero_state=*/false);
     show_edu_ = true;
@@ -594,7 +603,7 @@
   // Scroll the list to bottom when a new action is added.
   UpdateScrollView(/*scroll_to_bottom=*/true);
 
-  add_container_->UpdateAddButtonState(controller_->GetActiveActionsSize());
+  add_container_->UpdateAddButtonState(active_action_size);
 }
 
 void EditingList::OnActionRemoved(const Action& action) {
@@ -670,6 +679,10 @@
   return add_container_->add_button();
 }
 
+views::Button* EditingList::GetAddContainerButtonForTesting() const {
+  return views::AsViewClass<views::Button>(add_container_);
+}
+
 BEGIN_METADATA(EditingList)
 END_METADATA
 
diff --git a/chrome/browser/ash/arc/input_overlay/ui/editing_list.h b/chrome/browser/ash/arc/input_overlay/ui/editing_list.h
index b4ad2e4..47c2ef9 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/editing_list.h
+++ b/chrome/browser/ash/arc/input_overlay/ui/editing_list.h
@@ -23,6 +23,7 @@
 }  // namespace ui
 
 namespace views {
+class Button;
 class Label;
 class LabelButton;
 class ScrollView;
@@ -123,6 +124,7 @@
   bool IsKeyEditNudgeShownForTesting() const;
   ash::AnchoredNudge* GetKeyEditNudgeForTesting() const;
   views::LabelButton* GetAddButtonForTesting() const;
+  views::Button* GetAddContainerButtonForTesting() const;
 
   const raw_ptr<DisplayOverlayController> controller_;
 
diff --git a/chrome/browser/ash/arc/input_overlay/ui/editing_list_unittest.cc b/chrome/browser/ash/arc/input_overlay/ui/editing_list_unittest.cc
index f50cf4b..d4757ad8 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/editing_list_unittest.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/editing_list_unittest.cc
@@ -11,6 +11,7 @@
 #include "ash/system/toast/anchored_nudge_manager_impl.h"
 #include "base/check.h"
 #include "chrome/browser/ash/arc/input_overlay/actions/action.h"
+#include "chrome/browser/ash/arc/input_overlay/constants.h"
 #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h"
 #include "chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h"
 #include "chrome/browser/ash/arc/input_overlay/test/test_utils.h"
@@ -440,4 +441,22 @@
                                        kEditingListOffsetInsideMainWindow);
 }
 
+TEST_F(EditingListTest, TestMaximumActions) {
+  const size_t action_size = controller_->GetActiveActionsSize();
+  // Add new action util it reaches to the maximum.
+  EXPECT_GT(kMaxActionCount, action_size);
+  for (size_t i = 0; i < kMaxActionCount - action_size; i++) {
+    AddNewActionInCenter();
+    PressDoneButtonOnButtonOptionsMenu();
+  }
+
+  // Once the actions size reaches to the maximum, press add buttons shouldn't
+  // get into the button placement mode.
+  PressAddButton();
+  EXPECT_FALSE(GetTargetView());
+
+  PressAddContainerButton();
+  EXPECT_FALSE(GetTargetView());
+}
+
 }  // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/instance_throttle/DEPS b/chrome/browser/ash/arc/instance_throttle/DEPS
new file mode 100644
index 0000000..dd4d511
--- /dev/null
+++ b/chrome/browser/ash/arc/instance_throttle/DEPS
@@ -0,0 +1,21 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/instance_throttle",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sessions",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/intent_helper/DEPS b/chrome/browser/ash/arc/intent_helper/DEPS
new file mode 100644
index 0000000..fe2d189
--- /dev/null
+++ b/chrome/browser/ash/arc/intent_helper/DEPS
@@ -0,0 +1,34 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/intent_helper",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service/app_icon",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/policy/handlers",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/ash/system",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_list_observer.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/test",
+  "+chrome/browser/ui/zoom",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/keymaster/DEPS b/chrome/browser/ash/arc/keymaster/DEPS
index 9243dcd6..84917428 100644
--- a/chrome/browser/ash/arc/keymaster/DEPS
+++ b/chrome/browser/ash/arc/keymaster/DEPS
@@ -1,3 +1,21 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/keymaster",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/services/keymaster/public/mojom",
+
+  # Dependencies outside of //chrome:
   "+mojo/core/embedder",
 ]
diff --git a/chrome/browser/ash/arc/keymint/DEPS b/chrome/browser/ash/arc/keymint/DEPS
new file mode 100644
index 0000000..a8efc92
--- /dev/null
+++ b/chrome/browser/ash/arc/keymint/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/keymint",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/services/keymint/public/mojom",
+]
diff --git a/chrome/browser/ash/arc/kiosk/DEPS b/chrome/browser/ash/arc/kiosk/DEPS
new file mode 100644
index 0000000..09a9f4a0
--- /dev/null
+++ b/chrome/browser/ash/arc/kiosk/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/kiosk",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_mode/arc",
+]
diff --git a/chrome/browser/ash/arc/memory_pressure/DEPS b/chrome/browser/ash/arc/memory_pressure/DEPS
new file mode 100644
index 0000000..1fb222c
--- /dev/null
+++ b/chrome/browser/ash/arc/memory_pressure/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/memory_pressure",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc/process",
+  "+chrome/browser/memory",
+]
diff --git a/chrome/browser/ash/arc/metrics/DEPS b/chrome/browser/ash/arc/metrics/DEPS
new file mode 100644
index 0000000..c41b6d5
--- /dev/null
+++ b/chrome/browser/ash/arc/metrics/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/metrics",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/memory",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/arc/nearby_share/DEPS b/chrome/browser/ash/arc/nearby_share/DEPS
new file mode 100644
index 0000000..d390551c
--- /dev/null
+++ b/chrome/browser/ash/arc/nearby_share/DEPS
@@ -0,0 +1,33 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/nearby_share",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/fusebox",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/nearby_sharing",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sharesheet",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/webshare",
+  "+chrome/common/chrome_paths_internal.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/net/DEPS b/chrome/browser/ash/arc/net/DEPS
new file mode 100644
index 0000000..9686c33
--- /dev/null
+++ b/chrome/browser/ash/arc/net/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/net",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/net",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/notification/DEPS b/chrome/browser/ash/arc/notification/DEPS
new file mode 100644
index 0000000..d559795
--- /dev/null
+++ b/chrome/browser/ash/arc/notification/DEPS
@@ -0,0 +1,36 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/notification",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/login/demo_mode",
+  "+chrome/browser/ash/login/ui",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/oemcrypto/DEPS b/chrome/browser/ash/arc/oemcrypto/DEPS
new file mode 100644
index 0000000..f165208
--- /dev/null
+++ b/chrome/browser/ash/arc/oemcrypto/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/oemcrypto",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/arc/optin/DEPS b/chrome/browser/ash/arc/optin/DEPS
new file mode 100644
index 0000000..17a6aca
--- /dev/null
+++ b/chrome/browser/ash/arc/optin/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/optin",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/consent_auditor",
+  "+chrome/browser/extensions/api/settings_private",
+  "+chrome/browser/metrics",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/pip/DEPS b/chrome/browser/ash/arc/pip/DEPS
new file mode 100644
index 0000000..9363567f
--- /dev/null
+++ b/chrome/browser/ash/arc/pip/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/pip",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/picture_in_picture",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/policy/DEPS b/chrome/browser/ash/arc/policy/DEPS
new file mode 100644
index 0000000..2c24e62
--- /dev/null
+++ b/chrome/browser/ash/arc/policy/DEPS
@@ -0,0 +1,34 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/policy",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc/enterprise/cert_store",
+  "+chrome/browser/ash/arc/session",
+  "+chrome/browser/ash/arc/test",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/policy/arc",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/handlers",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chromeos/platform_keys",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/print_spooler/DEPS b/chrome/browser/ash/arc/print_spooler/DEPS
index 58877124..4b0465e2 100644
--- a/chrome/browser/ash/arc/print_spooler/DEPS
+++ b/chrome/browser/ash/arc/print_spooler/DEPS
@@ -1,3 +1,26 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/print_spooler",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/pdf",
+  "+chrome/browser/printing",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash",
+  "+chrome/services/printing/public/mojom",
+
+  # Dependencies outside of //chrome:
   "+components/printing/common",
 ]
diff --git a/chrome/browser/ash/arc/privacy_items/DEPS b/chrome/browser/ash/arc/privacy_items/DEPS
new file mode 100644
index 0000000..05ec4f3
--- /dev/null
+++ b/chrome/browser/ash/arc/privacy_items/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/privacy_items",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/process/DEPS b/chrome/browser/ash/arc/process/DEPS
new file mode 100644
index 0000000..162a686
--- /dev/null
+++ b/chrome/browser/ash/arc/process/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/process",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+]
diff --git a/chrome/browser/ash/arc/screen_capture/DEPS b/chrome/browser/ash/arc/screen_capture/DEPS
new file mode 100644
index 0000000..dd97db5
--- /dev/null
+++ b/chrome/browser/ash/arc/screen_capture/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/screen_capture",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/notifications",
+  "+chrome/browser/media/webrtc",
+  "+chrome/grit",
+]
diff --git a/chrome/browser/ash/arc/session/DEPS b/chrome/browser/ash/arc/session/DEPS
new file mode 100644
index 0000000..6a1a223
--- /dev/null
+++ b/chrome/browser/ash/arc/session/DEPS
@@ -0,0 +1,52 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/session",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/apps/app_service/publishers",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/app_restore",
+  "+chrome/browser/ash/apps",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/guest_os/public",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/policy/arc",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/test_support",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/consent_auditor",
+  "+chrome/browser/extensions/api/tabs",
+  "+chrome/browser/extensions/extension_tab_util.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/ash/multi_user",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/webui/ash",
+  "+chrome/common/channel_info.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/sharesheet/DEPS b/chrome/browser/ash/arc/sharesheet/DEPS
new file mode 100644
index 0000000..991a8e5
--- /dev/null
+++ b/chrome/browser/ash/arc/sharesheet/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/sharesheet",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/survey/DEPS b/chrome/browser/ash/arc/survey/DEPS
new file mode 100644
index 0000000..cf9bc4c
--- /dev/null
+++ b/chrome/browser/ash/arc/survey/DEPS
@@ -0,0 +1,21 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/survey",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/hats",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/test/DEPS b/chrome/browser/ash/arc/test/DEPS
new file mode 100644
index 0000000..9db87c7
--- /dev/null
+++ b/chrome/browser/ash/arc/test/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/test",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc/session",
+]
diff --git a/chrome/browser/ash/arc/tracing/DEPS b/chrome/browser/ash/arc/tracing/DEPS
index af06dabc..b3d8361 100644
--- a/chrome/browser/ash/arc/tracing/DEPS
+++ b/chrome/browser/ash/arc/tracing/DEPS
@@ -1,4 +1,29 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/tracing",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/app_restore",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sync",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+third_party/perfetto/include",
   "+third_party/perfetto/protos",
 ]
diff --git a/chrome/browser/ash/arc/tts/DEPS b/chrome/browser/ash/arc/tts/DEPS
index 33142b1..168f539 100644
--- a/chrome/browser/ash/arc/tts/DEPS
+++ b/chrome/browser/ash/arc/tts/DEPS
@@ -1,3 +1,22 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/tts",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/speech",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+content/public/browser/tts_controller.h",
 ]
diff --git a/chrome/browser/ash/arc/user_session/DEPS b/chrome/browser/ash/arc/user_session/DEPS
new file mode 100644
index 0000000..e51cec8
--- /dev/null
+++ b/chrome/browser/ash/arc/user_session/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/user_session",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/util/DEPS b/chrome/browser/ash/arc/util/DEPS
new file mode 100644
index 0000000..8c8bdd2b
--- /dev/null
+++ b/chrome/browser/ash/arc/util/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/util",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ui/browser_finder.h",
+]
diff --git a/chrome/browser/ash/arc/video/DEPS b/chrome/browser/ash/arc/video/DEPS
new file mode 100644
index 0000000..d586fe7
--- /dev/null
+++ b/chrome/browser/ash/arc/video/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/video",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/arc/vmm/DEPS b/chrome/browser/ash/arc/vmm/DEPS
new file mode 100644
index 0000000..29531d29
--- /dev/null
+++ b/chrome/browser/ash/arc/vmm/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/vmm",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/wallpaper/DEPS b/chrome/browser/ash/arc/wallpaper/DEPS
new file mode 100644
index 0000000..aa94f805
--- /dev/null
+++ b/chrome/browser/ash/arc/wallpaper/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/wallpaper",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/wallpaper_handlers",
+  "+chrome/browser/image_decoder",
+  "+chrome/browser/ui/ash",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/arc/window_predictor/DEPS b/chrome/browser/ash/arc/window_predictor/DEPS
new file mode 100644
index 0000000..bf889a7
--- /dev/null
+++ b/chrome/browser/ash/arc/window_predictor/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/arc/window_predictor",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/app_restore",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/assistant/DEPS b/chrome/browser/ash/assistant/DEPS
new file mode 100644
index 0000000..1b4eea7
--- /dev/null
+++ b/chrome/browser/ash/assistant/DEPS
@@ -0,0 +1,24 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/assistant",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/demo_mode",
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/attestation/DEPS b/chrome/browser/ash/attestation/DEPS
index f3cc83f3..ea56983d 100644
--- a/chrome/browser/ash/attestation/DEPS
+++ b/chrome/browser/ash/attestation/DEPS
@@ -1,4 +1,36 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/attestation",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/platform_keys/key_permissions",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/chromeos/platform_keys",
+  "+chrome/browser/extensions/chrome_extension_function_details.h",
+  "+chrome/browser/net",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+third_party/securemessage",
   "+third_party/boringssl/src/pki",
 ]
diff --git a/chrome/browser/ash/audio/DEPS b/chrome/browser/ash/audio/DEPS
new file mode 100644
index 0000000..e36418cc
--- /dev/null
+++ b/chrome/browser/ash/audio/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/audio",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/hats",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/authpolicy/DEPS b/chrome/browser/ash/authpolicy/DEPS
new file mode 100644
index 0000000..6d802ed
--- /dev/null
+++ b/chrome/browser/ash/authpolicy/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/authpolicy",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/browser_process.h",
+  "+chrome/common/pref_names.h",
+]
diff --git a/chrome/browser/ash/base/DEPS b/chrome/browser/ash/base/DEPS
new file mode 100644
index 0000000..d2f47a63
--- /dev/null
+++ b/chrome/browser/ash/base/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/base",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/session",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/profiles",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/bluetooth/DEPS b/chrome/browser/ash/bluetooth/DEPS
new file mode 100644
index 0000000..1bc38046
--- /dev/null
+++ b/chrome/browser/ash/bluetooth/DEPS
@@ -0,0 +1,28 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/bluetooth",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/hats",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/notifications",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/bluetooth_internals",
+  "+chrome/common/chrome_features.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/borealis/DEPS b/chrome/browser/ash/borealis/DEPS
index 735df9d3..89880da 100644
--- a/chrome/browser/ash/borealis/DEPS
+++ b/chrome/browser/ash/borealis/DEPS
@@ -4,6 +4,33 @@
 # chrome/browser/ui/views/borealis/.
 
 include_rules = [
-  # For access to the Borealis installer and splash screen.
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/borealis",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/hats",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sessions",
   "+chrome/browser/ui/views/borealis",
+  "+chrome/browser/ui/webui/ash/borealis_installer",
+  "+chrome/common/chrome_features.h",
+  "+chrome/grit",
+  "+chrome/test/base",
 ]
diff --git a/chrome/browser/ash/borealis/infra/DEPS b/chrome/browser/ash/borealis/infra/DEPS
new file mode 100644
index 0000000..1e4ac53
--- /dev/null
+++ b/chrome/browser/ash/borealis/infra/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/borealis/infra",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/borealis/testing",
+]
diff --git a/chrome/browser/ash/borealis/testing/DEPS b/chrome/browser/ash/borealis/testing/DEPS
new file mode 100644
index 0000000..5286a29
--- /dev/null
+++ b/chrome/browser/ash/borealis/testing/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/borealis/testing",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/borealis",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/login/users",
+  "+chrome/common/chrome_features.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/bruschetta/DEPS b/chrome/browser/ash/bruschetta/DEPS
new file mode 100644
index 0000000..64defd6
--- /dev/null
+++ b/chrome/browser/ash/bruschetta/DEPS
@@ -0,0 +1,30 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/bruschetta",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/content_settings",
+  "+chrome/browser/enterprise/util",
+  "+chrome/browser/extensions/api/terminal",
+  "+chrome/browser/extensions/cws_info_service.h",
+  "+chrome/browser/net",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/camera/DEPS b/chrome/browser/ash/camera/DEPS
new file mode 100644
index 0000000..bd95cb6
--- /dev/null
+++ b/chrome/browser/ash/camera/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/camera",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/hats",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/camera_mic/DEPS b/chrome/browser/ash/camera_mic/DEPS
index 07380813..49fed0c 100644
--- a/chrome/browser/ash/camera_mic/DEPS
+++ b/chrome/browser/ash/camera_mic/DEPS
@@ -1,3 +1,32 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/camera_mic",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/ash/borealis",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/video_conference",
+  "+chrome/browser/notifications",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/ash/settings/app_management",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   # Testing
   ".*_unittest\.cc": [
diff --git a/chrome/browser/ash/cert_provisioning/DEPS b/chrome/browser/ash/cert_provisioning/DEPS
new file mode 100644
index 0000000..7fd9521
--- /dev/null
+++ b/chrome/browser/ash/cert_provisioning/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/cert_provisioning",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/attestation",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/platform_keys",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/invalidation",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chromeos/platform_keys",
+  "+chrome/browser/invalidation",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/child_accounts/DEPS b/chrome/browser/ash/child_accounts/DEPS
new file mode 100644
index 0000000..15694d5
--- /dev/null
+++ b/chrome/browser/ash/child_accounts/DEPS
@@ -0,0 +1,59 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/child_accounts",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/app_restore",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/login/lock",
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/status_collector",
+  "+chrome/browser/ash/policy/uploading",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/extension_service_test_with_install.h",
+  "+chrome/browser/extensions/launch_util.h",
+  "+chrome/browser/extensions/test_extension_system.h",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/supervised_user",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_command_scheduler.h",
+  "+chrome/browser/web_applications/web_app_constants.h",
+  "+chrome/browser/web_applications/web_app_helpers.h",
+  "+chrome/browser/web_applications/web_app_install_finalizer.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_sync_bridge.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+  "+chrome/test/interaction",
+  "+chrome/test/supervised_user",
+  "+chrome/test/views",
+]
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/DEPS b/chrome/browser/ash/child_accounts/on_device_controls/DEPS
new file mode 100644
index 0000000..fa36684
--- /dev/null
+++ b/chrome/browser/ash/child_accounts/on_device_controls/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/child_accounts/on_device_controls",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/child_accounts/parent_access_code/DEPS b/chrome/browser/ash/child_accounts/parent_access_code/DEPS
new file mode 100644
index 0000000..ca08cdba
--- /dev/null
+++ b/chrome/browser/ash/child_accounts/parent_access_code/DEPS
@@ -0,0 +1,26 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/child_accounts/parent_access_code",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/profiles",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/child_accounts/time_limit_consistency_test/DEPS b/chrome/browser/ash/child_accounts/time_limit_consistency_test/DEPS
new file mode 100644
index 0000000..42418a6
--- /dev/null
+++ b/chrome/browser/ash/child_accounts/time_limit_consistency_test/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/child_accounts/time_limit_consistency_test",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/child_accounts",
+]
diff --git a/chrome/browser/ash/child_accounts/time_limits/DEPS b/chrome/browser/ash/child_accounts/time_limits/DEPS
new file mode 100644
index 0000000..fdac311
--- /dev/null
+++ b/chrome/browser/ash/child_accounts/time_limits/DEPS
@@ -0,0 +1,46 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/child_accounts/time_limits",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/child_accounts",
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/launch_util.h",
+  "+chrome/browser/extensions/test_extension_system.h",
+  "+chrome/browser/notifications",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_command_scheduler.h",
+  "+chrome/browser/web_applications/web_app_constants.h",
+  "+chrome/browser/web_applications/web_app_helpers.h",
+  "+chrome/browser/web_applications/web_app_install_finalizer.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_sync_bridge.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+  "+chrome/test/views",
+]
diff --git a/chrome/browser/ash/chromebox_for_meetings/DEPS b/chrome/browser/ash/chromebox_for_meetings/DEPS
new file mode 100644
index 0000000..a00aa1d
--- /dev/null
+++ b/chrome/browser/ash/chromebox_for_meetings/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/chromebox_for_meetings",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/media/webrtc",
+  "+chrome/browser/memory_details.h",
+  "+chrome/common/channel_info.h",
+]
diff --git a/chrome/browser/ash/chromebox_for_meetings/browser/DEPS b/chrome/browser/ash/chromebox_for_meetings/browser/DEPS
new file mode 100644
index 0000000..4224c814
--- /dev/null
+++ b/chrome/browser/ash/chromebox_for_meetings/browser/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/chromebox_for_meetings/browser",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/chromebox_for_meetings",
+  "+chrome/browser/memory_details.h",
+]
diff --git a/chrome/browser/ash/chromebox_for_meetings/device_info/DEPS b/chrome/browser/ash/chromebox_for_meetings/device_info/DEPS
new file mode 100644
index 0000000..6125ee7
--- /dev/null
+++ b/chrome/browser/ash/chromebox_for_meetings/device_info/DEPS
@@ -0,0 +1,21 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/chromebox_for_meetings/device_info",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/chromebox_for_meetings",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/settings",
+  "+chrome/common/channel_info.h",
+]
diff --git a/chrome/browser/ash/chromebox_for_meetings/diagnostics/DEPS b/chrome/browser/ash/chromebox_for_meetings/diagnostics/DEPS
new file mode 100644
index 0000000..be48a72f
--- /dev/null
+++ b/chrome/browser/ash/chromebox_for_meetings/diagnostics/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/chromebox_for_meetings/diagnostics",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/chromebox_for_meetings",
+]
diff --git a/chrome/browser/ash/chromebox_for_meetings/external_display_brightness/DEPS b/chrome/browser/ash/chromebox_for_meetings/external_display_brightness/DEPS
new file mode 100644
index 0000000..dae7dd3
--- /dev/null
+++ b/chrome/browser/ash/chromebox_for_meetings/external_display_brightness/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/chromebox_for_meetings/external_display_brightness",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/chromebox_for_meetings",
+]
diff --git a/chrome/browser/ash/chromebox_for_meetings/hotlog2/DEPS b/chrome/browser/ash/chromebox_for_meetings/hotlog2/DEPS
new file mode 100644
index 0000000..d3835fe
--- /dev/null
+++ b/chrome/browser/ash/chromebox_for_meetings/hotlog2/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/chromebox_for_meetings/hotlog2",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/chromebox_for_meetings",
+]
diff --git a/chrome/browser/ash/chromebox_for_meetings/logger/DEPS b/chrome/browser/ash/chromebox_for_meetings/logger/DEPS
new file mode 100644
index 0000000..62486172
--- /dev/null
+++ b/chrome/browser/ash/chromebox_for_meetings/logger/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/chromebox_for_meetings/logger",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/chromebox_for_meetings",
+  "+chrome/browser/ash/settings",
+]
diff --git a/chrome/browser/ash/chromebox_for_meetings/xu_camera/DEPS b/chrome/browser/ash/chromebox_for_meetings/xu_camera/DEPS
index 0d17b3db..2b89eb40 100644
--- a/chrome/browser/ash/chromebox_for_meetings/xu_camera/DEPS
+++ b/chrome/browser/ash/chromebox_for_meetings/xu_camera/DEPS
@@ -1,3 +1,22 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/chromebox_for_meetings/xu_camera",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/chromebox_for_meetings",
+  "+chrome/browser/media/webrtc",
+
+  # Dependencies outside of //chrome:
   "+components/media_device_salt",
 ]
diff --git a/chrome/browser/ash/crosapi/DEPS b/chrome/browser/ash/crosapi/DEPS
index 77bca698..3e1a56f 100644
--- a/chrome/browser/ash/crosapi/DEPS
+++ b/chrome/browser/ash/crosapi/DEPS
@@ -1,3 +1,95 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/crosapi",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/app_mode",
+  "+chrome/browser/apps/almanac_api_client",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/apps/digital_goods",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/chromeos/extensions/file_system_provider",
+  "+chrome/browser/chromeos/extensions/login_screen/login",
+  "+chrome/browser/chromeos/kcer",
+  "+chrome/browser/chromeos/platform_keys",
+  "+chrome/browser/chromeos/policy/dlp",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/device_identity",
+  "+chrome/browser/extensions/api/image_writer_private",
+  "+chrome/browser/extensions/api/printing",
+  "+chrome/browser/extensions/extension_apitest.h",
+  "+chrome/browser/extensions/extension_keeplist_chromeos.h",
+  "+chrome/browser/extensions/forced_extensions",
+  "+chrome/browser/file_system_access/cloud_identifier",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/media/router/discovery/access_code",
+  "+chrome/browser/metrics",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/printing",
+  "+chrome/browser/profiles",
+  "+chrome/browser/screen_ai",
+  "+chrome/browser/sharesheet",
+  "+chrome/browser/signin",
+  "+chrome/browser/speech",
+  "+chrome/browser/task_manager",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_navigator.h",
+  "+chrome/browser/ui/browser_navigator_params.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/views/task_manager_view.h",
+  "+chrome/browser/ui/web_applications/test",
+  "+chrome/browser/ui/webui/ash/app_install",
+  "+chrome/browser/ui/webui/ash/cloud_upload",
+  "+chrome/browser/ui/webui/ash/kerberos",
+  "+chrome/browser/ui/webui/ash/parent_access",
+  "+chrome/browser/ui/webui/chrome_web_ui_controller_factory.h",
+  "+chrome/browser/ui/webui/management",
+  "+chrome/browser/web_applications/preinstalled_web_app_config_utils.h",
+  "+chrome/browser/web_applications/preinstalled_web_app_utils.h",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_ui_manager.h",
+  "+chrome/browser/web_applications/web_app_utils.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/logging_chrome.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/printing",
+  "+chrome/common/ref_counted_util.h",
+  "+chrome/common/url_constants.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   "crosapi_ash\.cc": [
     "+services/video_capture/ash",
diff --git a/chrome/browser/ash/crosapi/echo_private_ash.cc b/chrome/browser/ash/crosapi/echo_private_ash.cc
index 8d57165..79887e5 100644
--- a/chrome/browser/ash/crosapi/echo_private_ash.cc
+++ b/chrome/browser/ash/crosapi/echo_private_ash.cc
@@ -15,6 +15,7 @@
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/crosapi/window_util.h"
+#include "chrome/browser/ash/login/startup_utils.h"
 #include "chrome/browser/ash/notifications/echo_dialog_view.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/browser_navigator.h"
@@ -32,11 +33,10 @@
 
 // Gets the Oobe timestamp on a sequence that allows file-access.
 std::string GetOobeTimestampBackground() {
-  base::File::Info file_info;
-  return base::GetFileInfo(base::FilePath("/home/chronos/.oobe_completed"),
-                           &file_info)
-             ? base::UnlocalizedTimeFormatWithPattern(
-                   file_info.creation_time, "y-M-d", icu::TimeZone::getGMT())
+  base::Time creation_time = ash::StartupUtils::GetTimeOfOobeFlagFileCreation();
+  return !creation_time.is_null()
+             ? base::UnlocalizedTimeFormatWithPattern(creation_time, "y-M-d",
+                                                      icu::TimeZone::getGMT())
              : std::string();
 }
 
diff --git a/chrome/browser/ash/crosapi/file_system_provider_service_ash.cc b/chrome/browser/ash/crosapi/file_system_provider_service_ash.cc
index 64d972f4..743f21b 100644
--- a/chrome/browser/ash/crosapi/file_system_provider_service_ash.cc
+++ b/chrome/browser/ash/crosapi/file_system_provider_service_ash.cc
@@ -8,6 +8,7 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/trace_event/trace_event.h"
 #include "base/types/expected.h"
+#include "chrome/browser/ash/file_system_provider/cloud_file_info.h"
 #include "chrome/browser/ash/file_system_provider/icon_set.h"
 #include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
@@ -203,10 +204,23 @@
   }
 }
 
+std::unique_ptr<ash::file_system_provider::CloudFileInfo> ParseCloudFileInfo(
+    mojom::CloudFileInfoPtr cloud_file_info) {
+  if (cloud_file_info.is_null()) {
+    return nullptr;
+  }
+  if (!cloud_file_info->version_tag.has_value()) {
+    return nullptr;
+  }
+  return std::make_unique<ash::file_system_provider::CloudFileInfo>(
+      cloud_file_info->version_tag.value());
+}
+
 // Convert the change from the mojom type to a native type.
 ProvidedFileSystemObserver::Change ParseChange(mojom::FSPChangePtr change) {
   return ProvidedFileSystemObserver::Change(
-      change->path, ParseChangeType(change->type));
+      change->path, ParseChangeType(change->type),
+      ParseCloudFileInfo(std::move(change->cloud_file_info)));
 }
 
 // Converts a list of child changes from the mojom type to a native type.
diff --git a/chrome/browser/ash/crosapi/prefs_ash.cc b/chrome/browser/ash/crosapi/prefs_ash.cc
index ab02e9ea..a4d4e593 100644
--- a/chrome/browser/ash/crosapi/prefs_ash.cc
+++ b/chrome/browser/ash/crosapi/prefs_ash.cc
@@ -75,6 +75,7 @@
            DefaultSearchManager::kDefaultSearchProviderDataPrefName},
           {mojom::PrefPath::kIsolatedWebAppsEnabled,
            ash::prefs::kIsolatedWebAppsEnabled},
+          {mojom::PrefPath::kMahiEnabled, ash::prefs::kMahiEnabled},
       });
   auto pref_name = kProfilePrefPathToName.find(path);
   DCHECK(pref_name != kProfilePrefPathToName.end());
@@ -301,7 +302,8 @@
     case mojom::PrefPath::kDefaultSearchProviderDataPrefName:
     case mojom::PrefPath::kIsolatedWebAppsEnabled:
     case mojom::PrefPath::kAccessibilityPdfOcrAlwaysActive:
-    case mojom::PrefPath::kAccessibilityReducedAnimationsEnabled: {
+    case mojom::PrefPath::kAccessibilityReducedAnimationsEnabled:
+    case mojom::PrefPath::kMahiEnabled: {
       if (!profile_prefs_registrar_) {
         LOG(WARNING) << "Primary profile is not yet initialized";
         return std::nullopt;
diff --git a/chrome/browser/ash/crosapi/test/DEPS b/chrome/browser/ash/crosapi/test/DEPS
index 989b6e8b..8dbed23 100644
--- a/chrome/browser/ash/crosapi/test/DEPS
+++ b/chrome/browser/ash/crosapi/test/DEPS
@@ -1,3 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/crosapi/test",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/common/chrome_switches.h",
+]
+
 specific_include_rules = {
   "ash_crosapi_tests_main.cc": [
     "+mojo/core/embedder",
diff --git a/chrome/browser/ash/crostini/DEPS b/chrome/browser/ash/crostini/DEPS
index 666a0dd..287ef2d0 100644
--- a/chrome/browser/ash/crostini/DEPS
+++ b/chrome/browser/ash/crostini/DEPS
@@ -1,3 +1,48 @@
 include_rules = [
-  "+chrome/browser/ui/views/crostini"
-]
\ No newline at end of file
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/crostini",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chrome_browser_main_extra_parts.h",
+  "+chrome/browser/chrome_browser_main.h",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/extensions/api/terminal",
+  "+chrome/browser/notifications",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_navigator.h",
+  "+chrome/browser/ui/browser_navigator_params.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_select_file_policy.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/views/crostini",
+  "+chrome/browser/ui/webui/ash",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+  "+chrome/test/views",
+]
diff --git a/chrome/browser/ash/crostini/ansible/DEPS b/chrome/browser/ash/crostini/ansible/DEPS
new file mode 100644
index 0000000..de23764
--- /dev/null
+++ b/chrome/browser/ash/crostini/ansible/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/crostini/ansible",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/views/crostini",
+  "+chrome/common/chrome_features.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/crostini/throttle/DEPS b/chrome/browser/ash/crostini/throttle/DEPS
new file mode 100644
index 0000000..96c3ed9
--- /dev/null
+++ b/chrome/browser/ash/crostini/throttle/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/crostini/throttle",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/cryptauth/DEPS b/chrome/browser/ash/cryptauth/DEPS
new file mode 100644
index 0000000..798cbf3
--- /dev/null
+++ b/chrome/browser/ash/cryptauth/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/cryptauth",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chrome_content_browser_client.h",
+  "+chrome/browser/gcm/instance_id",
+  "+chrome/browser/profiles",
+  "+chrome/common/pref_names.h",
+]
diff --git a/chrome/browser/ash/customization/DEPS b/chrome/browser/ash/customization/DEPS
new file mode 100644
index 0000000..33f6881e9
--- /dev/null
+++ b/chrome/browser/ash/customization/DEPS
@@ -0,0 +1,33 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/customization",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/ash/base",
+  "+chrome/browser/ash/extensions",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/net",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/extensions/external_loader.h",
+  "+chrome/browser/extensions/external_provider_impl.h",
+  "+chrome/browser/net",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/data_migration/DEPS b/chrome/browser/ash/data_migration/DEPS
new file mode 100644
index 0000000..86f93739
--- /dev/null
+++ b/chrome/browser/ash/data_migration/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/data_migration",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/nearby",
+  "+chrome/browser/nearby_sharing",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/dbus/DEPS b/chrome/browser/ash/dbus/DEPS
new file mode 100644
index 0000000..218ceba
--- /dev/null
+++ b/chrome/browser/ash/dbus/DEPS
@@ -0,0 +1,56 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/dbus",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_mode",
+  "+chrome/browser/ash/arc/fileapi",
+  "+chrome/browser/ash/arc/session",
+  "+chrome/browser/ash/arc/video",
+  "+chrome/browser/ash/borealis",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/exo",
+  "+chrome/browser/ash/fusebox",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/net",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/dlp",
+  "+chrome/browser/ash/policy/handlers",
+  "+chrome/browser/ash/power/ml",
+  "+chrome/browser/ash/printing",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/chromeos/policy/dlp",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/enterprise/browser_management",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/resource_coordinator",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/chrome_select_file_policy.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/ash/settings/app_management",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/dbus/vm/DEPS b/chrome/browser/ash/dbus/vm/DEPS
index 5f52bb3..e291521c 100644
--- a/chrome/browser/ash/dbus/vm/DEPS
+++ b/chrome/browser/ash/dbus/vm/DEPS
@@ -1,3 +1,38 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/dbus/vm",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/borealis",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/exo",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chromeos/policy/dlp",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/chrome_select_file_policy.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/ash/settings/app_management",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+]
+
 specific_include_rules = {
   "vm_applications_service_provider.cc": [
     "+chrome/browser/ui/views/select_file_dialog_extension.h",
diff --git a/chrome/browser/ash/device_name/DEPS b/chrome/browser/ash/device_name/DEPS
new file mode 100644
index 0000000..f755b7b
--- /dev/null
+++ b/chrome/browser/ash/device_name/DEPS
@@ -0,0 +1,26 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/device_name",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/handlers",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/profiles",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/device_name/docs/DEPS b/chrome/browser/ash/device_name/docs/DEPS
new file mode 100644
index 0000000..6e47d35
--- /dev/null
+++ b/chrome/browser/ash/device_name/docs/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/device_name/docs",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/device_sync/DEPS b/chrome/browser/ash/device_sync/DEPS
new file mode 100644
index 0000000..ce54020
--- /dev/null
+++ b/chrome/browser/ash/device_sync/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/device_sync",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/attestation",
+  "+chrome/browser/ash/cryptauth",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/gcm",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+]
diff --git a/chrome/browser/ash/diagnostics/DEPS b/chrome/browser/ash/diagnostics/DEPS
new file mode 100644
index 0000000..fbfbcbe7
--- /dev/null
+++ b/chrome/browser/ash/diagnostics/DEPS
@@ -0,0 +1,21 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/diagnostics",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/display/DEPS b/chrome/browser/ash/display/DEPS
index b8f3e610..1e3eb56 100644
--- a/chrome/browser/ash/display/DEPS
+++ b/chrome/browser/ash/display/DEPS
@@ -1,7 +1,25 @@
 include_rules = [
-  "-content",
-  "-chrome/browser",
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
   "+chrome/browser/ash/display",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/display",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
+  "-content",
 ]
 
 specific_include_rules = {
diff --git a/chrome/browser/ash/drive/DEPS b/chrome/browser/ash/drive/DEPS
index ea35402..2dc6c24 100644
--- a/chrome/browser/ash/drive/DEPS
+++ b/chrome/browser/ash/drive/DEPS
@@ -1,3 +1,41 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/drive",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/extensions/file_manager",
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/drivefs",
+  "+chrome/browser/download",
+  "+chrome/browser/drive",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_paths_internal.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+services/device/public/mojom",
 ]
diff --git a/chrome/browser/ash/drive/drive_integration_service.cc b/chrome/browser/ash/drive/drive_integration_service.cc
index c29047d..6cf39ec 100644
--- a/chrome/browser/ash/drive/drive_integration_service.cc
+++ b/chrome/browser/ash/drive/drive_integration_service.cc
@@ -50,6 +50,7 @@
 #include "chrome/common/pref_names.h"
 #include "chromeos/ash/components/drivefs/drivefs_bootstrap.h"
 #include "chromeos/ash/components/drivefs/drivefs_pinning_manager.h"
+#include "chromeos/ash/components/drivefs/mojom/notifications.mojom.h"
 #include "chromeos/components/drivefs/mojom/drivefs_native_messaging.mojom.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/crosapi/mojom/drive_integration_service.mojom.h"
@@ -585,6 +586,24 @@
     profile_->GetPrefs()->SetString(prefs::kDriveFsMirrorSyncMachineRootId, id);
   }
 
+  void PersistNotification(
+      drivefs::mojom::DriveFsNotificationPtr notification) override {
+    if (!ash::features::IsDriveFsMirroringEnabled()) {
+      return;
+    }
+    switch (notification->which()) {
+      case drivefs::mojom::DriveFsNotification::Tag::kMirrorDownloadDeleted:
+        persisted_notification_
+            [drivefs::mojom::DriveFsNotification::Tag::kMirrorDownloadDeleted]
+                .emplace_back(
+                    notification->get_mirror_download_deleted()->parent_title);
+        break;
+      case drivefs::mojom::DriveFsNotification::Tag::kUnknown:
+        LOG(ERROR) << "unknown notification received";
+        break;
+    }
+  }
+
   const raw_ptr<Profile> profile_;
   const raw_ptr<drivefs::DriveFsHost::MountObserver> mount_observer_;
 
@@ -599,6 +618,10 @@
   mojo::Remote<crosapi::mojom::DriveFsNativeMessageHostBridge>
       native_message_host_bridge_;
   base::OnceClosure pending_connect_to_extension_request_;
+  // Notification received from DriveFS which requires persistence.
+  std::unordered_map<drivefs::mojom::DriveFsNotification::Tag,
+                     std::vector<std::string>>
+      persisted_notification_;
 };
 
 DriveIntegrationService::DriveIntegrationService(
diff --git a/chrome/browser/ash/drive/fileapi/DEPS b/chrome/browser/ash/drive/fileapi/DEPS
new file mode 100644
index 0000000..f099c93
--- /dev/null
+++ b/chrome/browser/ash/drive/fileapi/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/drive/fileapi",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/fileapi",
+]
diff --git a/chrome/browser/ash/early_prefs/DEPS b/chrome/browser/ash/early_prefs/DEPS
new file mode 100644
index 0000000..54bd010
--- /dev/null
+++ b/chrome/browser/ash/early_prefs/DEPS
@@ -0,0 +1,21 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/early_prefs",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/common/chrome_paths.h",
+]
diff --git a/chrome/browser/ash/eche_app/DEPS b/chrome/browser/ash/eche_app/DEPS
index 6e3af17d..4dee556 100644
--- a/chrome/browser/ash/eche_app/DEPS
+++ b/chrome/browser/ash/eche_app/DEPS
@@ -1,3 +1,37 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/eche_app",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/accessibility",
+  "+chrome/browser/ash/device_sync",
+  "+chrome/browser/ash/multidevice_setup",
+  "+chrome/browser/ash/phonehub",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/secure_channel",
+  "+chrome/browser/ash/system_web_apps/types",
+  "+chrome/browser/notifications",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/common/channel_info.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   "eche_app_notification_controller\.cc": [
     "+ui/message_center/message_center.h",
diff --git a/chrome/browser/ash/enhanced_network_tts/DEPS b/chrome/browser/ash/enhanced_network_tts/DEPS
new file mode 100644
index 0000000..6168e105
--- /dev/null
+++ b/chrome/browser/ash/enhanced_network_tts/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/enhanced_network_tts",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/events/DEPS b/chrome/browser/ash/events/DEPS
index 0f8561b..26b9095 100644
--- a/chrome/browser/ash/events/DEPS
+++ b/chrome/browser/ash/events/DEPS
@@ -1,3 +1,26 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/events",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+  "+chrome/browser/extensions/extension_commands_global_registry.h",
+  "+chrome/browser/profiles",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   "event_rewriter_delegate_impl\.cc": [
     "+ui/message_center/message_center.h",
diff --git a/chrome/browser/ash/exo/DEPS b/chrome/browser/ash/exo/DEPS
new file mode 100644
index 0000000..6840223d
--- /dev/null
+++ b/chrome/browser/ash/exo/DEPS
@@ -0,0 +1,28 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/exo",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/bruschetta",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/extensions/file_manager",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/fusebox",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/extended_updates/DEPS b/chrome/browser/ash/extended_updates/DEPS
new file mode 100644
index 0000000..9d2eed9
--- /dev/null
+++ b/chrome/browser/ash/extended_updates/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/extended_updates",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/extended_updates/test/DEPS b/chrome/browser/ash/extended_updates/test/DEPS
new file mode 100644
index 0000000..418a84d
--- /dev/null
+++ b/chrome/browser/ash/extended_updates/test/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/extended_updates/test",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/extended_updates",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/extensions/DEPS b/chrome/browser/ash/extensions/DEPS
index 2f2c7e3..b3a1c385 100644
--- a/chrome/browser/ash/extensions/DEPS
+++ b/chrome/browser/ash/extensions/DEPS
@@ -1,3 +1,97 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/extensions",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app",
+  "+chrome/browser/app_mode",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/apps/platform_apps/api/media_galleries",
+  "+chrome/browser/ash",
+  "+chrome/browser/banners",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chromeos/drivefs",
+  "+chrome/browser/chromeos/extensions/login_screen",
+  "+chrome/browser/chromeos/policy/dlp",
+  "+chrome/browser/chromeos/upload_office_to_cloud",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/devtools",
+  "+chrome/browser/download",
+  "+chrome/browser/extensions/api/file_system",
+  "+chrome/browser/extensions/api/input_ime",
+  "+chrome/browser/extensions/api/messaging",
+  "+chrome/browser/extensions/api/settings_private",
+  "+chrome/browser/extensions/chrome_app_icon.h",
+  "+chrome/browser/extensions/chrome_extension_function_details.h",
+  "+chrome/browser/extensions/component_loader.h",
+  "+chrome/browser/extensions/crx_installer.h",
+  "+chrome/browser/extensions/devtools_util.h",
+  "+chrome/browser/extensions/extension_apitest.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/extension_service_test_base.h",
+  "+chrome/browser/extensions/extension_util.h",
+  "+chrome/browser/extensions/external_loader.h",
+  "+chrome/browser/extensions/external_provider_impl.h",
+  "+chrome/browser/extensions/install_observer.h",
+  "+chrome/browser/extensions/install_tracker.h",
+  "+chrome/browser/extensions/mock_crx_installer.h",
+  "+chrome/browser/extensions/pending_extension_manager.h",
+  "+chrome/browser/extensions/updater",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/metrics",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/pdf",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/resources/preinstalled_web_apps/internal",
+  "+chrome/browser/sharesheet",
+  "+chrome/browser/signin",
+  "+chrome/browser/speech",
+  "+chrome/browser/spellchecker",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/aura/accessibility",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_tabstrip.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/toolbar",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/browser/ui/webui/ash/cloud_upload",
+  "+chrome/browser/ui/webui/ash/crostini_installer",
+  "+chrome/browser/ui/webui/ash/manage_mirrorsync",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_registrar.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/services/media_gallery_util/public/cpp",
+  "+chrome/services/media_gallery_util/public/mojom",
+  "+chrome/services/pdf/public/mojom",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+third_party/ced",
 ]
diff --git a/chrome/browser/ash/extensions/autotest_private/DEPS b/chrome/browser/ash/extensions/autotest_private/DEPS
index dd42bd7..1c3140d 100644
--- a/chrome/browser/ash/extensions/autotest_private/DEPS
+++ b/chrome/browser/ash/extensions/autotest_private/DEPS
@@ -1,7 +1,71 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/extensions/autotest_private",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/app_list/search",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/assistant",
+  "+chrome/browser/ash/borealis",
+  "+chrome/browser/ash/bruschetta",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/fusebox",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/input_method",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/power/ml/smart_dim",
+  "+chrome/browser/ash/printing",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/ash/system",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/banners",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/extensions/component_loader.h",
+  "+chrome/browser/extensions/extension_apitest.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/aura/accessibility",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_tabstrip.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/toolbar",
   "+chrome/browser/ui/views/bruschetta",
   "+chrome/browser/ui/views/crostini",
   "+chrome/browser/ui/views/plugin_vm",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/browser/ui/webui/ash/crostini_installer",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_registrar.h",
+  "+chrome/common/extensions/api",
+  "+chrome/common/pref_names.h",
 ]
 
 specific_include_rules = {
diff --git a/chrome/browser/ash/extensions/file_manager/DEPS b/chrome/browser/ash/extensions/file_manager/DEPS
index 94cb2dd..dee01be 100644
--- a/chrome/browser/ash/extensions/file_manager/DEPS
+++ b/chrome/browser/ash/extensions/file_manager/DEPS
@@ -1,3 +1,81 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/extensions/file_manager",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/app_mode",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/apps/platform_apps/api/media_galleries",
+  "+chrome/browser/ash/app_list/search",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/file_system_provider",
+  "+chrome/browser/ash/fusebox",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/login/lock",
+  "+chrome/browser/ash/login/ui",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/policy/dlp",
+  "+chrome/browser/ash/policy/local_user_files",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/smb_client",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/drivefs",
+  "+chrome/browser/chromeos/policy/dlp",
+  "+chrome/browser/chromeos/upload_office_to_cloud",
+  "+chrome/browser/devtools",
+  "+chrome/browser/download",
+  "+chrome/browser/extensions/api/file_system",
+  "+chrome/browser/extensions/chrome_extension_function_details.h",
+  "+chrome/browser/extensions/devtools_util.h",
+  "+chrome/browser/extensions/extension_apitest.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/extension_util.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/metrics",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/pdf",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sharesheet",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/ash/holding_space",
+  "+chrome/browser/ui/ash/multi_user",
+  "+chrome/browser/ui/ash/system_web_apps",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/ash/cloud_upload",
+  "+chrome/browser/ui/webui/ash/manage_mirrorsync",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/services/media_gallery_util/public/cpp",
+  "+chrome/services/media_gallery_util/public/mojom",
+  "+chrome/services/pdf/public/mojom",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   "private_api_dialog.cc" : [
     "+chrome/browser/ui/views/select_file_dialog_extension.h",
diff --git a/chrome/browser/ash/extensions/language_packs/DEPS b/chrome/browser/ash/extensions/language_packs/DEPS
new file mode 100644
index 0000000..a5d93ff
--- /dev/null
+++ b/chrome/browser/ash/extensions/language_packs/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/extensions/language_packs",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/common/extensions/api",
+]
diff --git a/chrome/browser/ash/extensions/login_screen_ui/DEPS b/chrome/browser/ash/extensions/login_screen_ui/DEPS
new file mode 100644
index 0000000..e316b14f
--- /dev/null
+++ b/chrome/browser/ash/extensions/login_screen_ui/DEPS
@@ -0,0 +1,27 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/extensions/login_screen_ui",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/ui/login_screen_extension_ui",
+  "+chrome/browser/ash/policy/login",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/chromeos/extensions/login_screen",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/extensions/api",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/extensions/speech/DEPS b/chrome/browser/ash/extensions/speech/DEPS
new file mode 100644
index 0000000..9fa723cf
--- /dev/null
+++ b/chrome/browser/ash/extensions/speech/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/extensions/speech",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/extensions/extension_apitest.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/speech",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/common/extensions/api",
+]
diff --git a/chrome/browser/ash/extensions/users_private/DEPS b/chrome/browser/ash/extensions/users_private/DEPS
new file mode 100644
index 0000000..c633ad6e
--- /dev/null
+++ b/chrome/browser/ash/extensions/users_private/DEPS
@@ -0,0 +1,30 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/extensions/users_private",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/lock",
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/extensions/api/settings_private",
+  "+chrome/browser/extensions/extension_apitest.h",
+  "+chrome/browser/profiles",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions/api",
+]
diff --git a/chrome/browser/ash/file_manager/DEPS b/chrome/browser/ash/file_manager/DEPS
index e9b900e2..013175cb 100644
--- a/chrome/browser/ash/file_manager/DEPS
+++ b/chrome/browser/ash/file_manager/DEPS
@@ -1,5 +1,106 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/file_manager",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/app_list/search",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/base",
+  "+chrome/browser/ash/bruschetta",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/extensions/file_manager",
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/file_system_provider",
+  "+chrome/browser/ash/fusebox",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/login/demo_mode",
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/dlp",
+  "+chrome/browser/ash/policy/local_user_files",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/ash/smb_client",
+  "+chrome/browser/ash/system",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/chromeos/policy/dlp",
+  "+chrome/browser/chromeos/upload_office_to_cloud",
+  "+chrome/browser/download",
+  "+chrome/browser/enterprise/connectors",
+  "+chrome/browser/extensions/api/safe_browsing_private",
+  "+chrome/browser/extensions/chrome_test_extension_loader.h",
+  "+chrome/browser/extensions/component_loader.h",
+  "+chrome/browser/extensions/extension_apitest.h",
+  "+chrome/browser/extensions/extension_keeplist_chromeos.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/extension_tab_util.h",
+  "+chrome/browser/extensions/extension_util.h",
+  "+chrome/browser/extensions/launch_util.h",
+  "+chrome/browser/extensions/mixin_based_extension_apitest.h",
+  "+chrome/browser/extensions/test_extension_system.h",
+  "+chrome/browser/file_util_service.h",
+  "+chrome/browser/media_galleries/fileapi",
+  "+chrome/browser/notifications",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/safe_browsing/cloud_content_scanning",
+  "+chrome/browser/signin",
+  "+chrome/browser/sync_file_system",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/browser/ui/webui/ash/cloud_upload",
+  "+chrome/browser/ui/webui/ash/office_fallback",
+  "+chrome/browser/ui/webui/extensions",
+  "+chrome/browser/web_applications/isolated_web_apps",
+  "+chrome/browser/web_applications/os_integration",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_helpers.h",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_registry_update.h",
+  "+chrome/browser/web_applications/web_app_sync_bridge.h",
+  "+chrome/browser/web_applications/web_app_utils.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_content_client.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/services/file_util/public/cpp",
+  "+chrome/test/base",
+  "+chrome/test/interaction",
+]
+
 specific_include_rules = {
   "file_manager_browsertest_base.cc": [
     "+chrome/browser/ui/views/select_file_dialog_extension.h",
+    "+pdf/pdf_features.h",
   ],
 }
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
index 3ee8d3c2..436ca99 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
@@ -29,6 +29,7 @@
 #include "base/base_paths.h"
 #include "base/containers/circular_deque.h"
 #include "base/containers/contains.h"
+#include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
@@ -162,6 +163,7 @@
 #include "google_apis/drive/drive_api_parser.h"
 #include "media/base/media_switches.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "pdf/buildflags.h"
 #include "storage/browser/file_system/copy_or_move_operation_delegate.h"
 #include "storage/browser/file_system/external_mount_points.h"
 #include "storage/browser/file_system/file_system_context.h"
@@ -176,6 +178,10 @@
 #include "ui/shell_dialogs/select_file_policy.h"
 #include "url/url_util.h"
 
+#if BUILDFLAG(ENABLE_PDF)
+#include "pdf/pdf_features.h"
+#endif  // BUILDFLAG(ENABLE_PDF)
+
 using ::testing::_;
 
 class SelectFileDialogExtensionTestFactory
@@ -2665,6 +2671,18 @@
       ->InstallSystemAppsForTesting();
   const std::string full_test_name = GetFullTestCaseName();
   LOG(INFO) << "FileManagerBrowserTest::StartTest " << full_test_name;
+
+#if BUILDFLAG(ENABLE_PDF)
+  // TODO(crbug.com/326487542): Remove this once the tests pass for OOPIF PDF.
+  if (base::FeatureList::IsEnabled(chrome_pdf::features::kPdfOopif)) {
+    static const std::vector<std::string> kSkipTests = {
+        "openQuickViewPdf", "openQuickViewPdfPopup"};
+    if (base::Contains(kSkipTests, full_test_name)) {
+      GTEST_SKIP();
+    }
+  }
+#endif  // BUILDFLAG(ENABLE_PDF)
+
   static const base::FilePath test_extension_dir = base::FilePath(
       FILE_PATH_LITERAL("ui/file_manager/integration_tests/tsc"));
   LaunchExtension(base::DIR_GEN_TEST_DATA_ROOT, test_extension_dir,
diff --git a/chrome/browser/ash/file_manager/indexing/DEPS b/chrome/browser/ash/file_manager/indexing/DEPS
new file mode 100644
index 0000000..eb694c7
--- /dev/null
+++ b/chrome/browser/ash/file_manager/indexing/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/file_manager/indexing",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/file_manager/virtual_tasks/DEPS b/chrome/browser/ash/file_manager/virtual_tasks/DEPS
new file mode 100644
index 0000000..64f36a17
--- /dev/null
+++ b/chrome/browser/ash/file_manager/virtual_tasks/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/file_manager/virtual_tasks",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/fusebox",
+  "+chrome/browser/chromeos/upload_office_to_cloud",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/webui/ash/cloud_upload",
+  "+chrome/browser/web_applications/isolated_web_apps",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_utils.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/file_manager/volume_manager.cc b/chrome/browser/ash/file_manager/volume_manager.cc
index 57cd18a4..286cddbf 100644
--- a/chrome/browser/ash/file_manager/volume_manager.cc
+++ b/chrome/browser/ash/file_manager/volume_manager.cc
@@ -1690,8 +1690,7 @@
   documents_provider_root_manager_->AddObserver(this);
   // Registers a mount point for Android files only when the flag is enabled.
   RegisterAndroidFilesMountPoint();
-  if (arc::ArcSessionManager* const session_manager =
-          arc::ArcSessionManager::Get()) {
+  if (arc::ArcSessionManager::Get()) {
     arc::ArcSessionManager::Get()->AddObserver(this);
   } else {
     // Can be NULL only in tests.
diff --git a/chrome/browser/ash/file_suggest/DEPS b/chrome/browser/ash/file_suggest/DEPS
new file mode 100644
index 0000000..8102624
--- /dev/null
+++ b/chrome/browser/ash/file_suggest/DEPS
@@ -0,0 +1,29 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/file_suggest",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_list/search/files",
+  "+chrome/browser/ash/app_list/search/ranking",
+  "+chrome/browser/ash/app_list/search/util",
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/ash/holding_space",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/file_system_provider/DEPS b/chrome/browser/ash/file_system_provider/DEPS
new file mode 100644
index 0000000..acb66f0
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/DEPS
@@ -0,0 +1,40 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/file_system_provider",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/extensions/file_system_provider",
+  "+chrome/browser/extensions/chrome_app_icon_loader.h",
+  "+chrome/browser/extensions/extension_system_factory.h",
+  "+chrome/browser/extensions/window_controller_list.h",
+  "+chrome/browser/notifications",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/app_icon_loader.h",
+  "+chrome/browser/ui/ash/multi_user",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/file_system_provider/cloud_file_info.cc b/chrome/browser/ash/file_system_provider/cloud_file_info.cc
new file mode 100644
index 0000000..bf54b5dd
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/cloud_file_info.cc
@@ -0,0 +1,18 @@
+// 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/ash/file_system_provider/cloud_file_info.h"
+
+namespace ash::file_system_provider {
+
+CloudFileInfo::CloudFileInfo(const std::string& version_tag)
+    : version_tag(version_tag) {}
+
+CloudFileInfo::~CloudFileInfo() = default;
+
+bool CloudFileInfo::operator==(const CloudFileInfo& other) const {
+  return version_tag == other.version_tag;
+}
+
+}  // namespace ash::file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/cloud_file_info.h b/chrome/browser/ash/file_system_provider/cloud_file_info.h
new file mode 100644
index 0000000..f9282e6
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/cloud_file_info.h
@@ -0,0 +1,30 @@
+// 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_ASH_FILE_SYSTEM_PROVIDER_CLOUD_FILE_INFO_H_
+#define CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_CLOUD_FILE_INFO_H_
+
+#include <string>
+
+namespace ash::file_system_provider {
+
+// Represents version information relating to a particular file in cloud
+// storage.
+struct CloudFileInfo {
+  std::string version_tag;
+
+  explicit CloudFileInfo(const std::string& version_tag);
+
+  CloudFileInfo(const CloudFileInfo&) = delete;
+  CloudFileInfo& operator=(const CloudFileInfo&) = delete;
+
+  ~CloudFileInfo();
+
+  // Enables comparison for unit tests.
+  bool operator==(const CloudFileInfo&) const;
+};
+
+}  // namespace ash::file_system_provider
+
+#endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_CLOUD_FILE_INFO_H_
diff --git a/chrome/browser/ash/file_system_provider/cloud_file_system.cc b/chrome/browser/ash/file_system_provider/cloud_file_system.cc
index d9cc81d..7dd09fa5 100644
--- a/chrome/browser/ash/file_system_provider/cloud_file_system.cc
+++ b/chrome/browser/ash/file_system_provider/cloud_file_system.cc
@@ -16,6 +16,7 @@
 #include "base/strings/string_util.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/ash/file_manager/fileapi_util.h"
+#include "chrome/browser/ash/file_system_provider/cloud_file_info.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
 #include "chrome/browser/ash/file_system_provider/queue.h"
 #include "url/origin.h"
@@ -73,14 +74,22 @@
   NOTREACHED_NORETURN() << "Unknown ChangeType: " << type;
 }
 
+std::ostream& operator<<(std::ostream& out, CloudFileInfo* cloud_file_info) {
+  if (!cloud_file_info) {
+    return out << "none";
+  }
+  return out << "{version_tag = '" << cloud_file_info->version_tag << "'}";
+}
+
 std::ostream& operator<<(std::ostream& out,
                          ProvidedFileSystemObserver::Changes* changes) {
   if (!changes) {
     return out << "none";
   }
   for (size_t i = 0; i < changes->size(); ++i) {
-    const auto& [entry_path, change_type] = (*changes)[i];
-    out << entry_path << ": " << change_type;
+    const auto& [entry_path, change_type, cloud_file_info] = (*changes)[i];
+    out << entry_path << ": change_type = " << change_type
+        << ", cloud_file_info = " << cloud_file_info.get();
     if (i < changes->size() - 1) {
       out << ", ";
     }
@@ -88,13 +97,6 @@
   return out;
 }
 
-std::ostream& operator<<(std::ostream& out, CloudFileInfo* cloud_file_info) {
-  if (!cloud_file_info) {
-    return out << "none";
-  }
-  return out << "{version_tag = '" << cloud_file_info->version_tag << "'}";
-}
-
 const std::string GetVersionTag(CloudFileInfo* cloud_file_info) {
   return (cloud_file_info) ? cloud_file_info->version_tag : "";
 }
diff --git a/chrome/browser/ash/file_system_provider/content_cache/DEPS b/chrome/browser/ash/file_system_provider/content_cache/DEPS
new file mode 100644
index 0000000..f4779248
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/content_cache/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/file_system_provider/content_cache",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/file_system_provider",
+]
diff --git a/chrome/browser/ash/file_system_provider/content_cache/cache_manager.h b/chrome/browser/ash/file_system_provider/content_cache/cache_manager.h
index b7b7970..7ef531a6 100644
--- a/chrome/browser/ash/file_system_provider/content_cache/cache_manager.h
+++ b/chrome/browser/ash/file_system_provider/content_cache/cache_manager.h
@@ -22,8 +22,10 @@
 namespace ash::file_system_provider {
 
 // Callback type used when an FSP has been intiialized.
+using FileErrorOrContentCache =
+    base::FileErrorOr<std::unique_ptr<ContentCache>>;
 using FileErrorOrContentCacheCallback =
-    base::OnceCallback<void(base::FileErrorOr<std::unique_ptr<ContentCache>>)>;
+    base::OnceCallback<void(FileErrorOrContentCache)>;
 
 // The root directory name that houses all FSP content caches.
 inline constexpr char kFspContentCacheDirName[] = "FspContentCache";
diff --git a/chrome/browser/ash/file_system_provider/content_cache/cache_manager_impl.cc b/chrome/browser/ash/file_system_provider/content_cache/cache_manager_impl.cc
index 54e4189..8c1e0b8 100644
--- a/chrome/browser/ash/file_system_provider/content_cache/cache_manager_impl.cc
+++ b/chrome/browser/ash/file_system_provider/content_cache/cache_manager_impl.cc
@@ -7,8 +7,12 @@
 #include "base/base64.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/threading/sequence_bound.h"
 #include "base/types/expected.h"
+#include "chrome/browser/ash/file_system_provider/content_cache/cache_manager.h"
 #include "chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h"
+#include "chrome/browser/ash/file_system_provider/content_cache/context_database.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 
 namespace ash::file_system_provider {
@@ -24,6 +28,16 @@
   return base::File::FILE_OK;
 }
 
+OptionalContextDatabase InitializeContextDatabase(
+    const base::FilePath& db_path) {
+  std::unique_ptr<ContextDatabase> context_db =
+      std::make_unique<ContextDatabase>(db_path);
+  if (context_db->Initialize()) {
+    return context_db;
+  }
+  return std::nullopt;
+}
+
 }  // namespace
 
 CacheManagerImpl::CacheManagerImpl(const base::FilePath& profile_path,
@@ -52,14 +66,14 @@
   }
 
   if (in_memory_only_) {
-    OnInitializeForProvider(std::move(callback), cache_directory_path,
-                            base::File::FILE_OK);
+    OnProviderDirectoryCreationComplete(
+        std::move(callback), cache_directory_path, base::File::FILE_OK);
     return;
   }
 
   blocking_task_runner_->PostTaskAndReplyWithResult(
       FROM_HERE, base::BindOnce(&CreateProviderDirectory, cache_directory_path),
-      base::BindOnce(&CacheManagerImpl::OnInitializeForProvider,
+      base::BindOnce(&CacheManagerImpl::OnProviderDirectoryCreationComplete,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                      cache_directory_path));
 }
@@ -126,24 +140,57 @@
   observers_.RemoveObserver(observer);
 }
 
-void CacheManagerImpl::OnInitializeForProvider(
+void CacheManagerImpl::OnProviderDirectoryCreationComplete(
     FileErrorOrContentCacheCallback callback,
     base::FilePath cache_directory_path,
     base::File::Error result) {
-  const base::FilePath base64_encoded_provider_folder_name =
-      cache_directory_path.BaseName();
   if (result != base::File::FILE_OK) {
-    std::move(callback).Run(base::unexpected(result));
-  } else {
-    initialized_providers_.emplace(base64_encoded_provider_folder_name);
-    std::move(callback).Run(ContentCacheImpl::Create(cache_directory_path));
+    OnProviderInitializationComplete(cache_directory_path.BaseName(),
+                                     std::move(callback),
+                                     base::unexpected(result));
+    return;
   }
 
-  // Notify all observers.
-  for (auto& observer : observers_) {
-    observer.OnProviderInitializationComplete(
-        base64_encoded_provider_folder_name, result);
+  // Initialize the database task runner, the `ContextDatabase` will be created
+  // on this task runner and bound to the task runner on return.
+  scoped_refptr<base::SequencedTaskRunner> db_task_runner =
+      base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
+           base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
+  db_task_runner->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&InitializeContextDatabase,
+                     (in_memory_only_)
+                         ? base::FilePath()
+                         : cache_directory_path.Append("context.db")),
+      base::BindOnce(&CacheManagerImpl::OnProviderContextDatabaseSetup,
+                     weak_ptr_factory_.GetWeakPtr(), cache_directory_path,
+                     std::move(callback), db_task_runner));
+}
+
+void CacheManagerImpl::OnProviderContextDatabaseSetup(
+    const base::FilePath& cache_directory_path,
+    FileErrorOrContentCacheCallback callback,
+    scoped_refptr<base::SequencedTaskRunner> db_task_runner,
+    OptionalContextDatabase optional_context_db) {
+  const base::FilePath base64_encoded_provider_folder_name =
+      cache_directory_path.BaseName();
+
+  if (!optional_context_db.has_value()) {
+    OnProviderInitializationComplete(
+        base64_encoded_provider_folder_name, std::move(callback),
+        base::unexpected(base::File::FILE_ERROR_FAILED));
+    return;
   }
+
+  // Bind the `ContextDatabase` to the database task runner to ensure
+  // sequenced access via the `ContentCache` instance.
+  BoundContextDatabase context_db(db_task_runner,
+                                  std::move(optional_context_db.value()));
+  initialized_providers_.emplace(base64_encoded_provider_folder_name);
+  OnProviderInitializationComplete(
+      base64_encoded_provider_folder_name, std::move(callback),
+      ContentCacheImpl::Create(cache_directory_path, std::move(context_db)));
 }
 
 void CacheManagerImpl::OnUninitializeForProvider(
@@ -179,4 +226,18 @@
   return cache_directory_path;
 }
 
+void CacheManagerImpl::OnProviderInitializationComplete(
+    const base::FilePath& base64_encoded_provider_folder_name,
+    FileErrorOrContentCacheCallback callback,
+    FileErrorOrContentCache error_or_content_cache) {
+  base::File::Error result =
+      error_or_content_cache.error_or(base::File::FILE_OK);
+  std::move(callback).Run(std::move(error_or_content_cache));
+
+  for (Observer& observer : observers_) {
+    observer.OnProviderInitializationComplete(
+        base64_encoded_provider_folder_name, result);
+  }
+}
+
 }  // namespace ash::file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/content_cache/cache_manager_impl.h b/chrome/browser/ash/file_system_provider/content_cache/cache_manager_impl.h
index 5cfc933..67443f4 100644
--- a/chrome/browser/ash/file_system_provider/content_cache/cache_manager_impl.h
+++ b/chrome/browser/ash/file_system_provider/content_cache/cache_manager_impl.h
@@ -19,6 +19,7 @@
 #include "chrome/browser/ash/file_system_provider/abort_callback.h"
 #include "chrome/browser/ash/file_system_provider/content_cache/cache_manager.h"
 #include "chrome/browser/ash/file_system_provider/content_cache/content_cache.h"
+#include "chrome/browser/ash/file_system_provider/content_cache/context_database.h"
 
 namespace ash::file_system_provider {
 
@@ -50,11 +51,12 @@
   void RemoveObserver(Observer* observer) override;
 
  private:
-  // Responds to the FSP with the a `ContentCache` instance if directory
-  // creation was successful (or `in_memory_only` is true).
-  void OnInitializeForProvider(FileErrorOrContentCacheCallback callback,
-                               base::FilePath cache_directory_path,
-                               base::File::Error result);
+  // Attempt to initialize the context database if the directory creation was
+  // successful of the in_memory_only boolean is true.
+  void OnProviderDirectoryCreationComplete(
+      FileErrorOrContentCacheCallback callback,
+      base::FilePath cache_directory_path,
+      base::File::Error result);
 
   // Called once the deletion of the cache directory has been attempted.
   void OnUninitializeForProvider(
@@ -64,6 +66,21 @@
   const base::FilePath GetCacheDirectoryPath(
       const ProvidedFileSystemInfo& file_system_info);
 
+  // Called when the `ContextDatabase` has been setup: either a new database has
+  // been created OR the old database has been connected to.
+  void OnProviderContextDatabaseSetup(
+      const base::FilePath& cache_directory_path,
+      FileErrorOrContentCacheCallback callback,
+      scoped_refptr<base::SequencedTaskRunner> db_task_runner,
+      OptionalContextDatabase optional_context_db);
+
+  // When the initialization has finished, invoke the callback and notify the
+  // observers.
+  void OnProviderInitializationComplete(
+      const base::FilePath& base64_encoded_provider_folder_name,
+      FileErrorOrContentCacheCallback callback,
+      FileErrorOrContentCache error_or_content_cache);
+
   const base::FilePath root_content_cache_directory_;
   bool in_memory_only_ = false;
   std::set<base::FilePath> initialized_providers_;
diff --git a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.cc b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.cc
index e18fb13..a972f469 100644
--- a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.cc
+++ b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.cc
@@ -9,6 +9,7 @@
 #include "base/unguessable_token.h"
 #include "chrome/browser/ash/file_system_provider/cloud_file_system.h"
 #include "chrome/browser/ash/file_system_provider/content_cache/cache_file_context.h"
+#include "chrome/browser/ash/file_system_provider/content_cache/context_database.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
 #include "net/base/io_buffer.h"
 
@@ -53,17 +54,22 @@
 
 }  // namespace
 
-ContentCacheImpl::ContentCacheImpl(const base::FilePath& root_dir)
+ContentCacheImpl::ContentCacheImpl(const base::FilePath& root_dir,
+                                   BoundContextDatabase context_db)
     : root_dir_(root_dir),
       io_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
-           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {}
+           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
+      context_db_(std::move(context_db)) {}
 
-ContentCacheImpl::~ContentCacheImpl() = default;
+ContentCacheImpl::~ContentCacheImpl() {
+  context_db_.Reset();
+}
 
 std::unique_ptr<ContentCache> ContentCacheImpl::Create(
-    const base::FilePath& root_dir) {
-  return std::make_unique<ContentCacheImpl>(root_dir);
+    const base::FilePath& root_dir,
+    BoundContextDatabase context_db) {
+  return std::make_unique<ContentCacheImpl>(root_dir, std::move(context_db));
 }
 
 bool ContentCacheImpl::StartReadBytes(
diff --git a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h
index dbc3fe8f..ba7ccdc 100644
--- a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h
+++ b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h
@@ -8,8 +8,10 @@
 #include "base/files/file_error_or.h"
 #include "base/sequence_checker.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/threading/sequence_bound.h"
 #include "chrome/browser/ash/file_system_provider/content_cache/content_cache.h"
 #include "chrome/browser/ash/file_system_provider/content_cache/content_lru_cache.h"
+#include "chrome/browser/ash/file_system_provider/content_cache/context_database.h"
 #include "chrome/browser/ash/file_system_provider/opened_cloud_file.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
 
@@ -19,7 +21,8 @@
 // of orchestration between the LRU cache and the disk persistence layer.
 class ContentCacheImpl : public ContentCache {
  public:
-  explicit ContentCacheImpl(const base::FilePath& root_dir);
+  ContentCacheImpl(const base::FilePath& root_dir,
+                   BoundContextDatabase context_db);
 
   ContentCacheImpl(const ContentCacheImpl&) = delete;
   ContentCacheImpl& operator=(const ContentCacheImpl&) = delete;
@@ -27,7 +30,8 @@
   ~ContentCacheImpl() override;
 
   // Creates a `ContentCache` with the concrete implementation.
-  static std::unique_ptr<ContentCache> Create(const base::FilePath& root_dir);
+  static std::unique_ptr<ContentCache> Create(const base::FilePath& root_dir,
+                                              BoundContextDatabase context_db);
 
   bool StartReadBytes(
       const OpenedCloudFile& file,
@@ -64,6 +68,8 @@
   ContentLRUCache lru_cache_ GUARDED_BY_CONTEXT(sequence_checker_);
 
   scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
+  BoundContextDatabase context_db_;
+
   base::WeakPtrFactory<ContentCacheImpl> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl_unittest.cc b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl_unittest.cc
index 7167789..00fc53b2 100644
--- a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/rand_util.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
+#include "chrome/browser/ash/file_system_provider/content_cache/context_database.h"
 #include "chrome/browser/ash/file_system_provider/opened_cloud_file.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
 #include "net/base/io_buffer.h"
@@ -31,9 +32,24 @@
   void SetUp() override {
     EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
 
-    content_cache_ = std::make_unique<ContentCacheImpl>(temp_dir_.GetPath());
+    // Initialize a `ContextDatabase` in memory on a blocking task runner.
+    std::unique_ptr<ContextDatabase> context_db =
+        std::make_unique<ContextDatabase>(base::FilePath());
+    BoundContextDatabase db(
+        base::ThreadPool::CreateSequencedTaskRunner(
+            {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+             base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}),
+        std::move(context_db));
+    TestFuture<bool> future;
+    db.AsyncCall(&ContextDatabase::Initialize).Then(future.GetCallback());
+    EXPECT_TRUE(future.Get());
+
+    content_cache_ =
+        std::make_unique<ContentCacheImpl>(temp_dir_.GetPath(), std::move(db));
   }
 
+  void TearDown() override { content_cache_.reset(); }
+
   scoped_refptr<net::IOBufferWithSize> InitializeBufferWithRandBytes(int size) {
     scoped_refptr<net::IOBufferWithSize> buffer =
         base::MakeRefCounted<net::IOBufferWithSize>(size);
diff --git a/chrome/browser/ash/file_system_provider/content_cache/context_database.cc b/chrome/browser/ash/file_system_provider/content_cache/context_database.cc
new file mode 100644
index 0000000..3753337
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/content_cache/context_database.cc
@@ -0,0 +1,98 @@
+// 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/ash/file_system_provider/content_cache/context_database.h"
+
+#include "base/sequence_checker.h"
+#include "sql/init_status.h"
+#include "sql/transaction.h"
+
+namespace ash::file_system_provider {
+
+namespace {
+
+static constexpr char kItemsCreateTableSql[] =
+    // clang-format off
+    "CREATE TABLE IF NOT EXISTS items ("
+        "id INTEGER NOT NULL UNIQUE, "
+        "fsp_path TEXT NOT NULL, "
+        "version_tag TEXT NOT NULL, "
+        "accessed_time INTEGER NOT NULL, "
+        "UNIQUE(fsp_path, version_tag) ON CONFLICT REPLACE, "
+        "PRIMARY KEY(id AUTOINCREMENT))";
+// clang-format on
+
+}  // namespace
+
+ContextDatabase::ContextDatabase(const base::FilePath& db_path)
+    : db_path_(db_path), db_({sql::DatabaseOptions{}}) {
+  // Can be constructed on any sequence, the first call to `Initialize` should
+  // be made on the blocking task runner.
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+// The current database version number.
+constexpr int ContextDatabase::kCurrentVersionNumber = 1;
+
+// The oldest version that is still compatible with `kCurrentVersionNumber`.
+constexpr int ContextDatabase::kCompatibleVersionNumber = 1;
+
+bool ContextDatabase::Initialize() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  db_.set_histogram_tag("FSPContextDatabase");
+
+  // TODO(b/332636364): Once the logic for the database has landed, let's stop
+  // removing the database on every `Initialize` call.
+  if (!Raze()) {
+    LOG(ERROR) << "Failed to remove old database";
+    return false;
+  }
+
+  DCHECK(!db_.is_open()) << "Database is already open";
+
+  if (db_path_.empty() && !db_.OpenInMemory()) {
+    LOG(ERROR) << "In memory database initialization failed";
+    return false;
+  } else if (!db_path_.empty() && !db_.Open(db_path_)) {
+    LOG(ERROR) << "Initialization of '" << db_path_.value() << "' failed";
+    Raze();
+    return false;
+  }
+
+  sql::Transaction committer(&db_);
+  if (!committer.Begin()) {
+    LOG(ERROR) << "Can't start SQL transaction";
+    return false;
+  }
+
+  if (!db_.Execute(kItemsCreateTableSql)) {
+    LOG(ERROR) << "Can't setup items table";
+    Raze();
+    return false;
+  }
+
+  if (!meta_table_.Init(&db_, kCurrentVersionNumber,
+                        kCompatibleVersionNumber)) {
+    Raze();
+    return false;
+  }
+
+  return committer.Commit();
+}
+
+bool ContextDatabase::Raze() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  meta_table_.Reset();
+  if (!db_.is_open()) {
+    return true;
+  }
+
+  db_.Poison();
+  return sql::Database::Delete(db_path_);
+}
+
+ContextDatabase::~ContextDatabase() = default;
+
+}  // namespace ash::file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/content_cache/context_database.h b/chrome/browser/ash/file_system_provider/content_cache/context_database.h
new file mode 100644
index 0000000..9678ea75
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/content_cache/context_database.h
@@ -0,0 +1,51 @@
+// 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_ASH_FILE_SYSTEM_PROVIDER_CONTENT_CACHE_CONTEXT_DATABASE_H_
+#define CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_CONTENT_CACHE_CONTEXT_DATABASE_H_
+
+#include "base/sequence_checker.h"
+#include "base/threading/sequence_bound.h"
+#include "sql/database.h"
+#include "sql/meta_table.h"
+
+namespace ash::file_system_provider {
+
+// The persistent data store for items that are cached via an FSP mount. There
+// is 1:1 mapping of `ContextDatabase` per FSP mount and when the content cache
+// is removed, the database is removed with it.
+class ContextDatabase {
+ public:
+  explicit ContextDatabase(const base::FilePath& db_path);
+
+  ContextDatabase(const ContextDatabase&) = delete;
+  ContextDatabase& operator=(const ContextDatabase&) = delete;
+
+  ~ContextDatabase();
+
+  // Initialize the database either in-memory (if the constructor `db_path` was
+  // empty) or at the path specified by the `db_path` in the constructor.
+  bool Initialize();
+
+ private:
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Remove the database and poison any subsequent requests.
+  bool Raze();
+
+  static const int kCurrentVersionNumber;
+  static const int kCompatibleVersionNumber;
+
+  const base::FilePath db_path_;
+  sql::Database db_ GUARDED_BY_CONTEXT(sequence_checker_);
+  sql::MetaTable meta_table_ GUARDED_BY_CONTEXT(sequence_checker_);
+};
+
+using OptionalContextDatabase = std::optional<std::unique_ptr<ContextDatabase>>;
+using BoundContextDatabase =
+    base::SequenceBound<std::unique_ptr<ContextDatabase>>;
+
+}  // namespace ash::file_system_provider
+
+#endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_CONTENT_CACHE_CONTEXT_DATABASE_H_
diff --git a/chrome/browser/ash/file_system_provider/content_cache/context_database_unittest.cc b/chrome/browser/ash/file_system_provider/content_cache/context_database_unittest.cc
new file mode 100644
index 0000000..efd377a
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/content_cache/context_database_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/file_system_provider/content_cache/context_database.h"
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash::file_system_provider {
+namespace {
+
+class FileSystemProviderContextDatabaseTest : public testing::Test {
+ protected:
+  FileSystemProviderContextDatabaseTest() = default;
+  ~FileSystemProviderContextDatabaseTest() override = default;
+
+  void SetUp() override { EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+
+  base::test::TaskEnvironment task_environment_;
+  base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(FileSystemProviderContextDatabaseTest, DbCreatedOnInitialize) {
+  base::FilePath db_path = temp_dir_.GetPath().Append("context.db");
+  ContextDatabase context_db(db_path);
+  EXPECT_TRUE(context_db.Initialize());
+  EXPECT_TRUE(base::PathExists(db_path));
+}
+
+}  // namespace
+}  // namespace ash::file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/fileapi/DEPS b/chrome/browser/ash/file_system_provider/fileapi/DEPS
new file mode 100644
index 0000000..92282913
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/fileapi/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/file_system_provider/fileapi",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/file_system_provider",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/file_system_provider/operations/DEPS b/chrome/browser/ash/file_system_provider/operations/DEPS
new file mode 100644
index 0000000..1552689a
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/operations/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/file_system_provider/operations",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/file_system_provider",
+  "+chrome/common/extensions/api",
+]
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_interface.cc b/chrome/browser/ash/file_system_provider/provided_file_system_interface.cc
index 69f2ba496..8e9aa57 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_interface.cc
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_interface.cc
@@ -14,15 +14,6 @@
   return provider_name == other.provider_name && id == other.id;
 }
 
-CloudFileInfo::CloudFileInfo(const std::string& version_tag)
-    : version_tag(version_tag) {}
-
-CloudFileInfo::~CloudFileInfo() = default;
-
-bool CloudFileInfo::operator==(const CloudFileInfo& other) const {
-  return version_tag == other.version_tag;
-}
-
 EntryMetadata::EntryMetadata() = default;
 
 EntryMetadata::~EntryMetadata() = default;
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_interface.h b/chrome/browser/ash/file_system_provider/provided_file_system_interface.h
index d1b02a59..6cb2bbb 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_interface.h
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_interface.h
@@ -17,6 +17,7 @@
 #include "base/functional/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ash/file_system_provider/abort_callback.h"
+#include "chrome/browser/ash/file_system_provider/cloud_file_info.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_observer.h"
 #include "chrome/browser/ash/file_system_provider/watcher.h"
 #include "storage/browser/file_system/async_file_util.h"
@@ -45,22 +46,6 @@
   bool operator==(const CloudIdentifier&) const;
 };
 
-// Represents version information relating to a particular file in cloud
-// storage.
-struct CloudFileInfo {
-  std::string version_tag;
-
-  explicit CloudFileInfo(const std::string& version_tag);
-
-  CloudFileInfo(const CloudFileInfo&) = delete;
-  CloudFileInfo& operator=(const CloudFileInfo&) = delete;
-
-  ~CloudFileInfo();
-
-  // Enables comparison for unit tests.
-  bool operator==(const CloudFileInfo&) const;
-};
-
 // Represents metadata for either a file or a directory.
 struct EntryMetadata {
   EntryMetadata();
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_observer.cc b/chrome/browser/ash/file_system_provider/provided_file_system_observer.cc
index 81e9daf..b66da2d 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_observer.cc
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_observer.cc
@@ -8,9 +8,11 @@
 
 ProvidedFileSystemObserver::Change::Change(
     base::FilePath entry_path,
-    storage::WatcherManager::ChangeType change_type)
+    storage::WatcherManager::ChangeType change_type,
+    std::unique_ptr<CloudFileInfo> cloud_file_info)
     : entry_path(entry_path),
-      change_type(change_type) {}
+      change_type(change_type),
+      cloud_file_info(std::move(cloud_file_info)) {}
 
 ProvidedFileSystemObserver::Change::Change(Change&&) = default;
 
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_observer.h b/chrome/browser/ash/file_system_provider/provided_file_system_observer.h
index 2847c03..a09dace3 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_observer.h
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_observer.h
@@ -9,6 +9,7 @@
 
 #include "base/files/file_path.h"
 #include "base/functional/callback.h"
+#include "chrome/browser/ash/file_system_provider/cloud_file_info.h"
 #include "chrome/browser/ash/file_system_provider/watcher.h"
 #include "storage/browser/file_system/watcher_manager.h"
 
@@ -28,7 +29,8 @@
   // Describes a change related to a watched entry.
   struct Change {
     Change(base::FilePath entry_path,
-           storage::WatcherManager::ChangeType change_type);
+           storage::WatcherManager::ChangeType change_type,
+           std::unique_ptr<CloudFileInfo> cloud_file_info);
 
     // Not copyable.
     Change(const Change&) = delete;
@@ -41,6 +43,7 @@
 
     base::FilePath entry_path;
     storage::WatcherManager::ChangeType change_type;
+    std::unique_ptr<CloudFileInfo> cloud_file_info;
   };
 
   // Called when a watched entry is changed, including removals. |callback|
diff --git a/chrome/browser/ash/fileapi/DEPS b/chrome/browser/ash/fileapi/DEPS
new file mode 100644
index 0000000..d568400
--- /dev/null
+++ b/chrome/browser/ash/fileapi/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/fileapi",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/file_system_provider",
+  "+chrome/browser/ash/fusebox",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/extensions/extension_util.h",
+  "+chrome/browser/file_system_access",
+  "+chrome/browser/media_galleries/fileapi",
+  "+chrome/browser/profiles",
+  "+chrome/common/url_constants.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/fileapi/test/DEPS b/chrome/browser/ash/fileapi/test/DEPS
new file mode 100644
index 0000000..212d4ad
--- /dev/null
+++ b/chrome/browser/ash/fileapi/test/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/fileapi/test",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/fileapi",
+]
diff --git a/chrome/browser/ash/first_party_sets/DEPS b/chrome/browser/ash/first_party_sets/DEPS
new file mode 100644
index 0000000..c3c7d62
--- /dev/null
+++ b/chrome/browser/ash/first_party_sets/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/first_party_sets",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login",
+  "+chrome/browser/first_party_sets",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/first_run/DEPS b/chrome/browser/ash/first_run/DEPS
new file mode 100644
index 0000000..93e06e1
--- /dev/null
+++ b/chrome/browser/ash/first_run/DEPS
@@ -0,0 +1,29 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/first_run",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/ash/system_web_apps",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+]
diff --git a/chrome/browser/ash/floating_workspace/DEPS b/chrome/browser/ash/floating_workspace/DEPS
new file mode 100644
index 0000000..facd387
--- /dev/null
+++ b/chrome/browser/ash/floating_workspace/DEPS
@@ -0,0 +1,30 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/floating_workspace",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/ash/login/session",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/notifications",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sessions",
+  "+chrome/browser/sync",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/fusebox/DEPS b/chrome/browser/ash/fusebox/DEPS
new file mode 100644
index 0000000..63c68fa
--- /dev/null
+++ b/chrome/browser/ash/fusebox/DEPS
@@ -0,0 +1,21 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/fusebox",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/system_web_apps/apps",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/game_mode/DEPS b/chrome/browser/ash/game_mode/DEPS
new file mode 100644
index 0000000..68307455
--- /dev/null
+++ b/chrome/browser/ash/game_mode/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/game_mode",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/borealis",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/game_mode/testing/DEPS b/chrome/browser/ash/game_mode/testing/DEPS
new file mode 100644
index 0000000..d209a30
--- /dev/null
+++ b/chrome/browser/ash/game_mode/testing/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/game_mode/testing",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/geolocation/DEPS b/chrome/browser/ash/geolocation/DEPS
new file mode 100644
index 0000000..ddd65ae
--- /dev/null
+++ b/chrome/browser/ash/geolocation/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/geolocation",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/privacy_hub",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/growth/DEPS b/chrome/browser/ash/growth/DEPS
index 2a31205..57bf50e 100644
--- a/chrome/browser/ash/growth/DEPS
+++ b/chrome/browser/ash/growth/DEPS
@@ -1,3 +1,44 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/growth",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/login/demo_mode",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/feature_engagement",
+  "+chrome/browser/metrics",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/views/frame",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/browser/web_applications/mojom",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_command_scheduler.h",
+  "+chrome/browser/web_applications/web_app_constants.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_registrar.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+ui/message_center/message_center.h",
 ]
\ No newline at end of file
diff --git a/chrome/browser/ash/guest_os/DEPS b/chrome/browser/ash/guest_os/DEPS
index c1636fe..3d5e18b 100644
--- a/chrome/browser/ash/guest_os/DEPS
+++ b/chrome/browser/ash/guest_os/DEPS
@@ -1,3 +1,51 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/guest_os",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/ash/arc/session",
+  "+chrome/browser/ash/arc/test",
+  "+chrome/browser/ash/borealis",
+  "+chrome/browser/ash/bruschetta",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/exo",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/policy/local_user_files",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/smb_client",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/extensions/api/messaging",
+  "+chrome/browser/extensions/api/terminal",
+  "+chrome/browser/icon_transcoder",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/system_web_apps",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_tabstrip.h",
+  "+chrome/browser/ui/extensions",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+third_party/xdg_shared_mime_info",
 ]
diff --git a/chrome/browser/ash/guest_os/infra/DEPS b/chrome/browser/ash/guest_os/infra/DEPS
new file mode 100644
index 0000000..f18bd1ef
--- /dev/null
+++ b/chrome/browser/ash/guest_os/infra/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/guest_os/infra",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/borealis/testing",
+]
diff --git a/chrome/browser/ash/guest_os/public/DEPS b/chrome/browser/ash/guest_os/public/DEPS
new file mode 100644
index 0000000..0f4d73a
--- /dev/null
+++ b/chrome/browser/ash/guest_os/public/DEPS
@@ -0,0 +1,27 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/guest_os/public",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/borealis",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/policy/local_user_files",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/extensions/api/terminal",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/guest_os/virtual_machines/DEPS b/chrome/browser/ash/guest_os/virtual_machines/DEPS
new file mode 100644
index 0000000..3a736aa
--- /dev/null
+++ b/chrome/browser/ash/guest_os/virtual_machines/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/guest_os/virtual_machines",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/hats/DEPS b/chrome/browser/ash/hats/DEPS
new file mode 100644
index 0000000..a4ec744
--- /dev/null
+++ b/chrome/browser/ash/hats/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/hats",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/notifications",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser_dialogs.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/input_method/DEPS b/chrome/browser/ash/input_method/DEPS
index a9c723e..1fedcff 100644
--- a/chrome/browser/ash/input_method/DEPS
+++ b/chrome/browser/ash/input_method/DEPS
@@ -1,8 +1,51 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
   "-chrome",
-  "+chrome/browser",
-  "+chrome/common",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/input_method",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/login/demo_mode",
+  "+chrome/browser/ash/login/session",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/extensions/component_loader.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/feedback",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/manta",
+  "+chrome/browser/metrics",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/ash/keyboard",
+  "+chrome/browser/ui/aura/accessibility",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/ash/mako",
+  "+chrome/browser/ui/webui/ash/settings/search",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
   "+chrome/grit",
+
+  # Dependencies outside of //chrome:
   "-content",
 ]
 
diff --git a/chrome/browser/ash/input_method/ui/DEPS b/chrome/browser/ash/input_method/ui/DEPS
index d4012a6..e1e7130e 100644
--- a/chrome/browser/ash/input_method/ui/DEPS
+++ b/chrome/browser/ash/input_method/ui/DEPS
@@ -1,3 +1,20 @@
 include_rules = [
-  "+chrome/app/vector_icons"
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/input_method/ui",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/ash/input_method",
+  "+chrome/grit",
 ]
diff --git a/chrome/browser/ash/kcer/DEPS b/chrome/browser/ash/kcer/DEPS
new file mode 100644
index 0000000..907aa53
--- /dev/null
+++ b/chrome/browser/ash/kcer/DEPS
@@ -0,0 +1,28 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/kcer",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/login/lock",
+  "+chrome/browser/ash/login/saml",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/kcer",
+  "+chrome/browser/net",
+  "+chrome/browser/profiles",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/kcer/nssdb_migration/DEPS b/chrome/browser/ash/kcer/nssdb_migration/DEPS
new file mode 100644
index 0000000..03f9818
--- /dev/null
+++ b/chrome/browser/ash/kcer/nssdb_migration/DEPS
@@ -0,0 +1,25 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/kcer/nssdb_migration",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/kcer",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/chromeos/kcer",
+  "+chrome/browser/net",
+  "+chrome/browser/profiles",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/kcer/nssdb_migration/pkcs12_migrator_unittest.cc b/chrome/browser/ash/kcer/nssdb_migration/pkcs12_migrator_unittest.cc
index e8d036c..79e7337d 100644
--- a/chrome/browser/ash/kcer/nssdb_migration/pkcs12_migrator_unittest.cc
+++ b/chrome/browser/ash/kcer/nssdb_migration/pkcs12_migrator_unittest.cc
@@ -166,7 +166,8 @@
 }
 
 // Test that Pkcs12Migrator can successfully migrate multiple certs.
-TEST_F(KcerPkcs12MigratorTest, MultipleCertsMigratedSuccess) {
+// TODO(crbug.com/333795379): Re-enable this test
+TEST_F(KcerPkcs12MigratorTest, DISABLED_MultipleCertsMigratedSuccess) {
   ImportPkcs12("client.p12", NssSlot::kPublic);
   ImportPkcs12("client_with_ec_key.p12", NssSlot::kPublic);
 
@@ -210,7 +211,8 @@
 
 // Test that Pkcs12Migrator doesn't migrate certs that are already present in
 // the private slot (i.e. in Chaps), but migrates the other ones.
-TEST_F(KcerPkcs12MigratorTest, SomeCertsAlreadyExist) {
+// TODO(crbug.com/333795379): Re-enable this test
+TEST_F(KcerPkcs12MigratorTest, DISABLED_SomeCertsAlreadyExist) {
   ImportPkcs12("client.p12", NssSlot::kPublic);
   ImportPkcs12("client_with_ec_key.p12", NssSlot::kPublic);
   ImportPkcs12("client.p12", NssSlot::kPrivate);
diff --git a/chrome/browser/ash/kerberos/DEPS b/chrome/browser/ash/kerberos/DEPS
new file mode 100644
index 0000000..73dd088a
--- /dev/null
+++ b/chrome/browser/ash/kerberos/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/kerberos",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/authpolicy",
+  "+chrome/browser/ash/login/session",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/language_packs/DEPS b/chrome/browser/ash/language_packs/DEPS
new file mode 100644
index 0000000..29b9470b
--- /dev/null
+++ b/chrome/browser/ash/language_packs/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/language_packs",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/lock_screen_apps/DEPS b/chrome/browser/ash/lock_screen_apps/DEPS
new file mode 100644
index 0000000..07c8796
--- /dev/null
+++ b/chrome/browser/ash/lock_screen_apps/DEPS
@@ -0,0 +1,34 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/lock_screen_apps",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/extensions/extension_assets_manager.h",
+  "+chrome/browser/extensions/extension_browsertest.h",
+  "+chrome/browser/extensions/extension_management.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/test_extension_system.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/apps",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/DEPS b/chrome/browser/ash/login/DEPS
index b8318d74..43ab04c3 100644
--- a/chrome/browser/ash/login/DEPS
+++ b/chrome/browser/ash/login/DEPS
@@ -1,4 +1,135 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app",
+  "+chrome/browser/about_flags.h",
+  "+chrome/browser/app_mode",
+  "+chrome/browser/apps",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/chrome_browser_main_extra_parts.h",
+  "+chrome/browser/chrome_browser_main.h",
+  "+chrome/browser/chromeos/app_mode",
+  "+chrome/browser/command_updater_delegate.h",
+  "+chrome/browser/command_updater_impl.h",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/consent_auditor",
+  "+chrome/browser/content_settings",
+  "+chrome/browser/device_identity",
+  "+chrome/browser/enterprise/connectors/device_trust/common",
+  "+chrome/browser/enterprise/util",
+  "+chrome/browser/extensions/api/quick_unlock_private",
+  "+chrome/browser/extensions/browsertest_util.h",
+  "+chrome/browser/extensions/chrome_extension_test_notification_observer.h",
+  "+chrome/browser/extensions/chrome_test_extension_loader.h",
+  "+chrome/browser/extensions/extension_browsertest.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/extension_service_test_base.h",
+  "+chrome/browser/extensions/extension_system_factory.h",
+  "+chrome/browser/extensions/extension_tab_util.h",
+  "+chrome/browser/extensions/external_loader.h",
+  "+chrome/browser/extensions/external_provider_impl.h",
+  "+chrome/browser/extensions/forced_extensions",
+  "+chrome/browser/extensions/permissions",
+  "+chrome/browser/extensions/policy_handlers.h",
+  "+chrome/browser/extensions/test_extension_system.h",
+  "+chrome/browser/first_run",
+  "+chrome/browser/google",
+  "+chrome/browser/image_decoder",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/media/webrtc",
+  "+chrome/browser/metrics",
+  "+chrome/browser/nearby_sharing",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/password_manager",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/renderer_preferences_util.h",
+  "+chrome/browser/rlz",
+  "+chrome/browser/safe_browsing",
+  "+chrome/browser/scalable_iph",
+  "+chrome/browser/screen_ai",
+  "+chrome/browser/sessions",
+  "+chrome/browser/signin",
+  "+chrome/browser/speech/extension_api",
+  "+chrome/browser/ssl",
+  "+chrome/browser/supervised_user",
+  "+chrome/browser/sync",
+  "+chrome/browser/themes",
+  "+chrome/browser/trusted_vault",
+  "+chrome/browser/ui/apps",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/aura/accessibility",
+  "+chrome/browser/ui/autofill",
+  "+chrome/browser/ui/browser_dialogs.h",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_list_observer.h",
+  "+chrome/browser/ui/browser_navigator.h",
+  "+chrome/browser/ui/browser_navigator_params.h",
+  "+chrome/browser/ui/browser_tabstrip.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/chrome_select_file_policy.h",
+  "+chrome/browser/ui/chrome_web_modal_dialog_manager_delegate.h",
+  "+chrome/browser/ui/content_settings",
+  "+chrome/browser/ui/exclusive_access",
+  "+chrome/browser/ui/login",
+  "+chrome/browser/ui/managed_ui.h",
+  "+chrome/browser/ui/scoped_tabbed_browser_displayer.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/startup",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/test",
+  "+chrome/browser/ui/toolbar",
+  "+chrome/browser/ui/view_ids.h",
+  "+chrome/browser/ui/views/frame",
+  "+chrome/browser/ui/webui/ash",
+  "+chrome/browser/ui/webui/chrome_web_contents_handler.h",
+  "+chrome/browser/ui/webui/feedback",
+  "+chrome/browser/ui/webui/help",
+  "+chrome/browser/ui/webui/signin",
+  "+chrome/browser/unified_consent",
+  "+chrome/browser/web_applications/external_install_options.h",
+  "+chrome/browser/web_applications/externally_managed_app_manager.h",
+  "+chrome/browser/web_applications/web_app_constants.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/logging_chrome.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/url_constants.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+  "+chrome/test/interaction",
+  "+chrome/test/views",
+
+  # Dependencies outside of //chrome:
   "+components/trusted_vault",
   "+extensions/components/native_app_window",
   "+third_party/securemessage",
diff --git a/chrome/browser/ash/login/app_mode/DEPS b/chrome/browser/ash/login/app_mode/DEPS
new file mode 100644
index 0000000..21dca4d1
--- /dev/null
+++ b/chrome/browser/ash/login/app_mode/DEPS
@@ -0,0 +1,63 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/app_mode",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/app_mode/test",
+  "+chrome/browser/ash/accessibility",
+  "+chrome/browser/ash/app_mode",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/remote_commands",
+  "+chrome/browser/ash/policy/test_support",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chromeos/app_mode",
+  "+chrome/browser/device_identity",
+  "+chrome/browser/extensions/browsertest_util.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/extension_service_test_base.h",
+  "+chrome/browser/extensions/forced_extensions",
+  "+chrome/browser/extensions/policy_handlers.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/speech/extension_api",
+  "+chrome/browser/ui/ash/keyboard",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_navigator.h",
+  "+chrome/browser/ui/browser_navigator_params.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/test",
+  "+chrome/browser/ui/views/frame",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/browser/web_applications/external_install_options.h",
+  "+chrome/browser/web_applications/externally_managed_app_manager.h",
+  "+chrome/browser/web_applications/web_app_constants.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/auth/DEPS b/chrome/browser/ash/login/auth/DEPS
new file mode 100644
index 0000000..ea37873
--- /dev/null
+++ b/chrome/browser/ash/login/auth/DEPS
@@ -0,0 +1,25 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/auth",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/login",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/common/chrome_paths.h",
+]
diff --git a/chrome/browser/ash/login/demo_mode/DEPS b/chrome/browser/ash/login/demo_mode/DEPS
new file mode 100644
index 0000000..b7995c05
--- /dev/null
+++ b/chrome/browser/ash/login/demo_mode/DEPS
@@ -0,0 +1,40 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/demo_mode",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/apps/platform_apps",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chrome_browser_main_extra_parts.h",
+  "+chrome/browser/chrome_browser_main.h",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/extensions/external_loader.h",
+  "+chrome/browser/extensions/external_provider_impl.h",
+  "+chrome/browser/metrics",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/apps",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_list_observer.h",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/extensions",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/enrollment/DEPS b/chrome/browser/ash/login/enrollment/DEPS
new file mode 100644
index 0000000..5c17778
--- /dev/null
+++ b/chrome/browser/ash/login/enrollment/DEPS
@@ -0,0 +1,37 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/enrollment",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_mode",
+  "+chrome/browser/ash/attestation",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/enrollment",
+  "+chrome/browser/ash/policy/handlers",
+  "+chrome/browser/ash/policy/server_backed_state",
+  "+chrome/browser/ash/policy/test_support",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/net",
+  "+chrome/browser/prefs",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/extensions/DEPS b/chrome/browser/ash/login/extensions/DEPS
new file mode 100644
index 0000000..1ed3f0c
--- /dev/null
+++ b/chrome/browser/ash/login/extensions/DEPS
@@ -0,0 +1,27 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/extensions",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/lock",
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/extension_system_factory.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/lock/DEPS b/chrome/browser/ash/login/lock/DEPS
new file mode 100644
index 0000000..20e8921
--- /dev/null
+++ b/chrome/browser/ash/login/lock/DEPS
@@ -0,0 +1,38 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/lock",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/lock_screen_apps",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/ash/system",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/exclusive_access",
+  "+chrome/browser/ui/webui/ash/lock_screen_reauth",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/oobe_quick_start/BUILD.gn b/chrome/browser/ash/login/oobe_quick_start/BUILD.gn
index 6d8d3dc..f9c992d 100644
--- a/chrome/browser/ash/login/oobe_quick_start/BUILD.gn
+++ b/chrome/browser/ash/login/oobe_quick_start/BUILD.gn
@@ -25,6 +25,7 @@
     "//chromeos/ash/components/attestation:attestation",
     "//chromeos/ash/components/dbus/attestation:attestation_proto",
     "//chromeos/ash/components/dbus/constants:constants",
+    "//chromeos/ash/components/nearby/common/connections_manager:connections_manager",
     "//chromeos/ash/components/quick_start:quick_start",
     "//chromeos/ash/services/nearby/public/mojom",
     "//components/account_id:account_id",
@@ -57,6 +58,7 @@
     "//chrome/test:test_support",
     "//chromeos/ash/components/attestation:test_support",
     "//chromeos/ash/components/dbus/constants:constants",
+    "//chromeos/ash/components/nearby/common/connections_manager:connections_manager",
     "//chromeos/ash/components/quick_start:test_support",
     "//components/account_id:account_id",
     "//google_apis:google_apis",
diff --git a/chrome/browser/ash/login/oobe_quick_start/DEPS b/chrome/browser/ash/login/oobe_quick_start/DEPS
index 8a5f025..69a4fa6 100644
--- a/chrome/browser/ash/login/oobe_quick_start/DEPS
+++ b/chrome/browser/ash/login/oobe_quick_start/DEPS
@@ -1,4 +1,27 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/oobe_quick_start",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/accessibility",
+  "+chrome/browser/ash/nearby",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/nearby_sharing",
+  "+chrome/common/channel_info.h",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+components/qr_code_generator/qr_code_generator.h",
   "+components/endpoint_fetcher",
 ]
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn b/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn
index bd35222..86c2861 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn
@@ -14,6 +14,7 @@
     "//chrome/browser/ash/login/oobe_quick_start:oobe_quick_start_pref_names",
     "//chrome/browser/ash/nearby:quick_start_connectivity_service",
     "//chrome/browser/nearby_sharing/public/cpp",
+    "//chromeos/ash/components/nearby/common/connections_manager:connections_manager",
     "//chromeos/ash/components/quick_start",
     "//chromeos/ash/services/nearby/public/cpp",
     "//chromeos/ash/services/nearby/public/mojom",
@@ -84,6 +85,7 @@
     "//chrome/browser/ash:test_support",
     "//chrome/browser/nearby_sharing/public/cpp",
     "//chrome/test:test_support",
+    "//chromeos/ash/components/nearby/common/connections_manager:connections_manager",
     "//chromeos/ash/components/quick_start",
     "//chromeos/ash/components/quick_start:test_support",
     "//chromeos/ash/services/nearby/public/mojom",
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc
index 0fc6481..8aa6a65 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc
@@ -58,8 +58,8 @@
 const int kFlowTypeTargetChallenge = 2;
 const int kDeviceTypeChrome = 7;
 const char kPostTransferActionKey[] = "PostTransferAction";
-const char kURIKey[] = "uri";
-const char kURIValue[] =
+const char kPostTransferActionURIKey[] = "uri";
+const char kPostTransferActionURIValue[] =
     "intent:#Intent;action=com.google.android.gms.quickstart.LANDING_SCREEN;"
     "package=com.google.android.gms;end";
 
@@ -505,9 +505,9 @@
             kAccountRequirementSingle);
   EXPECT_EQ(*bootstrap_options.FindInt(kFlowTypeKey), kFlowTypeTargetChallenge);
   EXPECT_EQ(*bootstrap_options.FindInt(kDeviceTypeKey), kDeviceTypeChrome);
-  EXPECT_EQ(
-      *bootstrap_options.FindDict(kPostTransferActionKey)->FindString(kURIKey),
-      kURIValue);
+  EXPECT_EQ(*bootstrap_options.FindDict(kPostTransferActionKey)
+                 ->FindString(kPostTransferActionURIKey),
+            kPostTransferActionURIValue);
 
   // Emulate a BootstrapConfigurations response.
   std::vector<uint8_t> instance_id = {0x01, 0x02, 0x03};
@@ -1079,6 +1079,9 @@
 
   EXPECT_EQ(parsed_payload.FindInt(kBootstrapStateKey),
             kBootstrapStateComplete);
+  EXPECT_EQ(*parsed_payload.FindDict(kPostTransferActionKey)
+                 ->FindString(kPostTransferActionURIKey),
+            kPostTransferActionURIValue);
   TestMessageMetrics(
       /*should_succeed=*/true,
       /*message_type=*/QuickStartMetrics::MessageType::kBootstrapStateComplete,
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl.cc
index c72e57c9..ceb8d99 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl.cc
@@ -19,7 +19,7 @@
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/fast_pair_advertiser.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h"
 #include "chrome/browser/ash/nearby/quick_start_connectivity_service.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/components/quick_start/logging.h"
 #include "chromeos/ash/components/quick_start/quick_start_metrics.h"
 #include "device/bluetooth/bluetooth_adapter.h"
@@ -338,8 +338,9 @@
   // post-launch.
   quick_start_connectivity_service_->GetNearbyConnectionsManager()
       ->StartAdvertising(
-          GenerateEndpointInfo(), /*listener=*/this, PowerLevel::kHighPower,
-          DataUsage::kOffline,
+          GenerateEndpointInfo(), /*listener=*/this,
+          NearbyConnectionsManager::PowerLevel::kHighPower,
+          nearby_share::mojom::DataUsage::kOffline,
           base::BindOnce(&TargetDeviceConnectionBrokerImpl::
                              OnStartNearbyConnectionsAdvertising,
                          weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl.h b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl.h
index f36b7af6..e5a2508 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl.h
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl.h
@@ -12,7 +12,7 @@
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/connection.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/session_context.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "device/bluetooth/bluetooth_adapter_factory.h"
 #include "mojo/public/cpp/bindings/shared_remote.h"
 
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc
index 062ba2f..8fd2b93f 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc
@@ -21,10 +21,10 @@
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_factory.h"
 #include "chrome/browser/ash/nearby/fake_quick_start_connectivity_service.h"
 #include "chrome/browser/nearby_sharing/fake_nearby_connection.h"
-#include "chrome/browser/nearby_sharing/public/cpp/fake_nearby_connections_manager.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chrome/test/base/scoped_testing_local_state.h"
 #include "chrome/test/base/testing_browser_process.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/fake_nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/components/quick_start/fake_quick_start_decoder.h"
 #include "chromeos/constants/devicetype.h"
 #include "device/bluetooth/bluetooth_adapter_factory.h"
@@ -651,7 +651,7 @@
           &TargetDeviceConnectionBrokerImplTest::StartAdvertisingResultCallback,
           weak_ptr_factory_.GetWeakPtr()));
   EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising());
-  EXPECT_EQ(PowerLevel::kHighPower,
+  EXPECT_EQ(NearbyConnectionsManager::PowerLevel::kHighPower,
             fake_nearby_connections_manager_->advertising_power_level());
   EXPECT_TRUE(start_advertising_callback_called_);
   EXPECT_TRUE(start_advertising_callback_success_);
@@ -788,7 +788,7 @@
   // begin Nearby Connections advertising without ever Fast Pair advertising.
   EXPECT_EQ(0u, fast_pair_advertiser_factory_->StartAdvertisingCount());
   EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising());
-  EXPECT_EQ(PowerLevel::kHighPower,
+  EXPECT_EQ(NearbyConnectionsManager::PowerLevel::kHighPower,
             fake_nearby_connections_manager_->advertising_power_level());
   EXPECT_TRUE(start_advertising_callback_called_);
   EXPECT_TRUE(start_advertising_callback_success_);
diff --git a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h
index fa5d458..264220e 100644
--- a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h
+++ b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h
@@ -19,7 +19,7 @@
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/qr_code.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h"
 #include "chrome/browser/ash/login/oobe_quick_start/second_device_auth_broker.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/components/quick_start/types.h"
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom.h"
 #include "mojo/public/cpp/bindings/shared_remote.h"
diff --git a/chrome/browser/ash/login/osauth/DEPS b/chrome/browser/ash/login/osauth/DEPS
new file mode 100644
index 0000000..0bea4ff3f
--- /dev/null
+++ b/chrome/browser/ash/login/osauth/DEPS
@@ -0,0 +1,24 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/osauth",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/quick_unlock/DEPS b/chrome/browser/ash/login/quick_unlock/DEPS
new file mode 100644
index 0000000..aaedb11
--- /dev/null
+++ b/chrome/browser/ash/login/quick_unlock/DEPS
@@ -0,0 +1,24 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/quick_unlock",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/auth",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/quickstart_controller.cc b/chrome/browser/ash/login/quickstart_controller.cc
index 1fb820a..abfa624 100644
--- a/chrome/browser/ash/login/quickstart_controller.cc
+++ b/chrome/browser/ash/login/quickstart_controller.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/ash/login/wizard_context.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/ui/webui/ash/login/add_child_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/consumer_update_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/error_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h"
@@ -27,6 +28,7 @@
 #include "chrome/browser/ui/webui/ash/login/network_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/online_login_utils.h"
 #include "chrome/browser/ui/webui/ash/login/quick_start_screen_handler.h"
+#include "chrome/browser/ui/webui/ash/login/update_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/user_creation_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/welcome_screen_handler.h"
 #include "chromeos/ash/components/login/auth/public/user_context.h"
@@ -80,16 +82,26 @@
 
 QuickStartMetrics::ScreenName ScreenNameFromOobeScreenId(
     OobeScreenId screen_id) {
-  // TODO(b/298042953): Check Screen IDs for Unicorn account setup flow.
-  if (screen_id == ConsumerUpdateScreenView::kScreenId) {
-    // TODO(b/298042953): Update Screen ID when the new OOBE Checking for
-    // update and determining device configuration screen is added.
+  if (screen_id == WelcomeView::kScreenId) {
+    return QuickStartMetrics::ScreenName::kWelcomeScreen;
+  } else if (screen_id == NetworkScreenView::kScreenId) {
+    return QuickStartMetrics::ScreenName::kNetworkScreen;
+  } else if (screen_id == GaiaInfoScreenView::kScreenId) {
+    return QuickStartMetrics::ScreenName::kGaiaInfoScreen;
+  } else if (screen_id == GaiaView::kScreenId) {
+    return QuickStartMetrics::ScreenName::kGaiaScreen;
+  } else if (screen_id == UpdateView::kScreenId) {
     return QuickStartMetrics::ScreenName::
         kCheckingForUpdateAndDeterminingDeviceConfiguration;
   } else if (screen_id == UserCreationView::kScreenId) {
     return QuickStartMetrics::ScreenName::kChooseChromebookSetup;
+  } else if (screen_id == ConsumerUpdateScreenView::kScreenId) {
+    return QuickStartMetrics::ScreenName::kConsumerUpdate;
+  } else if (screen_id == AddChildScreenView::kScreenId) {
+    return QuickStartMetrics::ScreenName::kAddChild;
+  } else {
+    return QuickStartMetrics::ScreenName::kOther;
   }
-  return QuickStartMetrics::ScreenName::kOther;
 }
 
 bool IsConnectedToWiFi() {
diff --git a/chrome/browser/ash/login/reporting/DEPS b/chrome/browser/ash/login/reporting/DEPS
new file mode 100644
index 0000000..3df0d0245
--- /dev/null
+++ b/chrome/browser/ash/login/reporting/DEPS
@@ -0,0 +1,29 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/reporting",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_mode",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/reporting",
+  "+chrome/browser/ash/policy/status_collector",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/extensions/browsertest_util.h",
+  "+chrome/browser/policy/messaging_layer/proto/synced",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/saml/DEPS b/chrome/browser/ash/login/saml/DEPS
new file mode 100644
index 0000000..0faa59c
--- /dev/null
+++ b/chrome/browser/ash/login/saml/DEPS
@@ -0,0 +1,40 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/saml",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/content_settings",
+  "+chrome/browser/enterprise/connectors/device_trust/common",
+  "+chrome/browser/extensions/extension_browsertest.h",
+  "+chrome/browser/media/webrtc",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/webui/ash/in_session_password_change",
+  "+chrome/browser/ui/webui/ash/lock_screen_reauth",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/browser/ui/webui/signin",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/screens/DEPS b/chrome/browser/ash/login/screens/DEPS
new file mode 100644
index 0000000..549f7474
--- /dev/null
+++ b/chrome/browser/ash/login/screens/DEPS
@@ -0,0 +1,52 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/screens",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/app_mode",
+  "+chrome/browser/apps",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/consent_auditor",
+  "+chrome/browser/enterprise/util",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/metrics",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/speech/extension_api",
+  "+chrome/browser/sync",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/managed_ui.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/ash",
+  "+chrome/browser/ui/webui/signin/ash",
+  "+chrome/browser/unified_consent",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/url_constants.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/screens/quick_start_screen_browsertest.cc b/chrome/browser/ash/login/screens/quick_start_screen_browsertest.cc
index 2fe405e..491fd65 100644
--- a/chrome/browser/ash/login/screens/quick_start_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/quick_start_screen_browsertest.cc
@@ -575,6 +575,9 @@
       kScreenOpenedHistogram,
       quick_start::QuickStartMetrics::ScreenName::kQSWifiCredentialsReceived,
       0);
+  histogram_tester.ExpectBucketCount(
+      kScreenOpenedHistogram,
+      quick_start::QuickStartMetrics::ScreenName::kChooseChromebookSetup, 0);
 
   EnterQuickStartFlowFromWelcomeScreen();
   histogram_tester.ExpectBucketCount(
@@ -599,6 +602,10 @@
   SkipUpdateScreenOnBrandedBuilds();
   WaitForUserCreationAndTriggerPersonalFlow();
 
+  histogram_tester.ExpectBucketCount(
+      kScreenOpenedHistogram,
+      quick_start::QuickStartMetrics::ScreenName::kChooseChromebookSetup, 1);
+
   // The flow continues to QuickStart upon reaching the GaiaInfoScreen or
   // GaiaScreen.
   OobeScreenWaiter(QuickStartView::kScreenId).Wait();
diff --git a/chrome/browser/ash/login/session/DEPS b/chrome/browser/ash/login/session/DEPS
new file mode 100644
index 0000000..5e2c8e30
--- /dev/null
+++ b/chrome/browser/ash/login/session/DEPS
@@ -0,0 +1,48 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/session",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/about_flags.h",
+  "+chrome/browser/app_mode",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/first_run",
+  "+chrome/browser/google",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/net",
+  "+chrome/browser/password_manager",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/rlz",
+  "+chrome/browser/scalable_iph",
+  "+chrome/browser/screen_ai",
+  "+chrome/browser/signin",
+  "+chrome/browser/supervised_user/child_accounts",
+  "+chrome/browser/sync",
+  "+chrome/browser/trusted_vault",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/startup",
+  "+chrome/browser/ui/webui/ash",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/logging_chrome.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/signin/DEPS b/chrome/browser/ash/login/signin/DEPS
new file mode 100644
index 0000000..d7bf2d4
--- /dev/null
+++ b/chrome/browser/ash/login/signin/DEPS
@@ -0,0 +1,48 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/signin",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/account_manager",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/extensions/chrome_extension_test_notification_observer.h",
+  "+chrome/browser/extensions/chrome_test_extension_loader.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/supervised_user",
+  "+chrome/browser/sync",
+  "+chrome/browser/ui/ash/multi_user",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_tabstrip.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/scoped_tabbed_browser_displayer.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/browser/ui/webui/signin/ash",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/smart_lock/DEPS b/chrome/browser/ash/login/smart_lock/DEPS
new file mode 100644
index 0000000..9ee4192a
--- /dev/null
+++ b/chrome/browser/ash/login/smart_lock/DEPS
@@ -0,0 +1,35 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/smart_lock",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/device_sync",
+  "+chrome/browser/ash/login/session",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/multidevice_setup",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/secure_channel",
+  "+chrome/browser/notifications",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/ash/multidevice_setup",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/startup_utils.cc b/chrome/browser/ash/login/startup_utils.cc
index 8784e08..bcec3490 100644
--- a/chrome/browser/ash/login/startup_utils.cc
+++ b/chrome/browser/ash/login/startup_utils.cc
@@ -208,12 +208,20 @@
 }
 
 // static
-base::TimeDelta StartupUtils::GetTimeSinceOobeFlagFileCreation() {
+base::Time StartupUtils::GetTimeOfOobeFlagFileCreation() {
   const base::FilePath oobe_complete_flag_path = GetOobeCompleteFlagPath();
   base::File::Info file_info;
-  if (base::GetFileInfo(oobe_complete_flag_path, &file_info))
-    return base::Time::Now() - file_info.creation_time;
-  return base::TimeDelta();
+  if (base::GetFileInfo(oobe_complete_flag_path, &file_info)) {
+    return file_info.creation_time;
+  }
+  return base::Time();
+}
+
+// static
+base::TimeDelta StartupUtils::GetTimeSinceOobeFlagFileCreation() {
+  base::Time creation_time = GetTimeOfOobeFlagFileCreation();
+  return !creation_time.is_null() ? base::Time::Now() - creation_time
+                                  : base::TimeDelta();
 }
 
 // static
diff --git a/chrome/browser/ash/login/startup_utils.h b/chrome/browser/ash/login/startup_utils.h
index 3915e51..992bd3e 100644
--- a/chrome/browser/ash/login/startup_utils.h
+++ b/chrome/browser/ash/login/startup_utils.h
@@ -12,8 +12,9 @@
 class PrefRegistrySimple;
 
 namespace base {
+class Time;
 class TimeDelta;
-}
+}  // namespace base
 
 namespace ash {
 
@@ -41,6 +42,9 @@
   // Stores the next OOBE screen after updating and rebooting to be resumed.
   static void SaveScreenAfterConsumerUpdate(const std::string& screen);
 
+  // Returns the time the OOBE flag file was created.
+  static base::Time GetTimeOfOobeFlagFileCreation();
+
   // Returns the time since the OOBE flag file was created.
   static base::TimeDelta GetTimeSinceOobeFlagFileCreation();
 
diff --git a/chrome/browser/ash/login/test/DEPS b/chrome/browser/ash/login/test/DEPS
new file mode 100644
index 0000000..db05db1
--- /dev/null
+++ b/chrome/browser/ash/login/test/DEPS
@@ -0,0 +1,38 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/test",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/extensions",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/net",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/enrollment",
+  "+chrome/browser/ash/policy/test_support",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chrome_browser_main_extra_parts.h",
+  "+chrome/browser/chrome_browser_main.h",
+  "+chrome/browser/extensions/chrome_test_extension_loader.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/browser/ui/webui/signin",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/ui/DEPS b/chrome/browser/ash/login/ui/DEPS
index c5d2fd34..b1f8674 100644
--- a/chrome/browser/ash/login/ui/DEPS
+++ b/chrome/browser/ash/login/ui/DEPS
@@ -1,6 +1,62 @@
 include_rules = [
-  "!chrome/browser/ui/views/location_bar/location_bar_view.h",
-  "!chrome/browser/ui/views/toolbar/reload_button.h",
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/ui",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/command_updater_delegate.h",
+  "+chrome/browser/command_updater_impl.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/media/webrtc",
+  "+chrome/browser/password_manager",
+  "+chrome/browser/profiles",
+  "+chrome/browser/renderer_preferences_util.h",
+  "+chrome/browser/safe_browsing",
+  "+chrome/browser/sessions",
+  "+chrome/browser/signin",
+  "+chrome/browser/ssl",
+  "+chrome/browser/themes",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/autofill",
+  "+chrome/browser/ui/browser_dialogs.h",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_list_observer.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/chrome_web_modal_dialog_manager_delegate.h",
+  "+chrome/browser/ui/content_settings",
+  "+chrome/browser/ui/login",
+  "+chrome/browser/ui/toolbar",
+  "+chrome/browser/ui/views/location_bar/location_bar_view.h",
+  "+chrome/browser/ui/views/toolbar/reload_button.h",
+  "+chrome/browser/ui/view_ids.h",
+  "+chrome/browser/ui/webui/ash",
+  "+chrome/browser/ui/webui/chrome_web_contents_handler.h",
+  "+chrome/browser/ui/webui/feedback",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+  "+chrome/test/views",
 ]
 
 specific_include_rules = {
diff --git a/chrome/browser/ash/login/users/DEPS b/chrome/browser/ash/login/users/DEPS
new file mode 100644
index 0000000..b8b800f
--- /dev/null
+++ b/chrome/browser/ash/login/users/DEPS
@@ -0,0 +1,49 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/users",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_mode",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/external_data",
+  "+chrome/browser/ash/policy/handlers",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/ash/system",
+  "+chrome/browser/ash/wallpaper_handlers",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/extensions/extension_tab_util.h",
+  "+chrome/browser/extensions/permissions",
+  "+chrome/browser/image_decoder",
+  "+chrome/browser/policy/networking",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_select_file_policy.h",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/login/version_updater/DEPS b/chrome/browser/ash/login/version_updater/DEPS
new file mode 100644
index 0000000..3942a26
--- /dev/null
+++ b/chrome/browser/ash/login/version_updater/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/login/version_updater",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/mahi/DEPS b/chrome/browser/ash/mahi/DEPS
new file mode 100644
index 0000000..334b4e4a
--- /dev/null
+++ b/chrome/browser/ash/mahi/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/mahi",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/manta",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/mahi/mahi_ui_browsertest.cc b/chrome/browser/ash/mahi/mahi_ui_browsertest.cc
index 6f83be0..a521f09f 100644
--- a/chrome/browser/ash/mahi/mahi_ui_browsertest.cc
+++ b/chrome/browser/ash/mahi/mahi_ui_browsertest.cc
@@ -11,8 +11,8 @@
 #include "base/auto_reset.h"
 #include "base/command_line.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ui/views/mahi/mahi_menu_constants.h"
 #include "chrome/browser/ui/views/mahi/mahi_menu_view.h"
-#include "chrome/browser/ui/views/mahi/mahi_menu_view_ids.h"
 #include "chrome/test/base/chrome_test_utils.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chromeos/components/mahi/public/cpp/mahi_switches.h"
diff --git a/chrome/browser/ash/mall/DEPS b/chrome/browser/ash/mall/DEPS
new file mode 100644
index 0000000..c307792
--- /dev/null
+++ b/chrome/browser/ash/mall/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/mall",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/almanac_api_client",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/mobile/DEPS b/chrome/browser/ash/mobile/DEPS
new file mode 100644
index 0000000..a61fa5e
--- /dev/null
+++ b/chrome/browser/ash/mobile/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/mobile",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/mojo_service_manager/DEPS b/chrome/browser/ash/mojo_service_manager/DEPS
new file mode 100644
index 0000000..52f81dd
--- /dev/null
+++ b/chrome/browser/ash/mojo_service_manager/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/mojo_service_manager",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/multidevice_debug/DEPS b/chrome/browser/ash/multidevice_debug/DEPS
new file mode 100644
index 0000000..256a8aa
--- /dev/null
+++ b/chrome/browser/ash/multidevice_debug/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/multidevice_debug",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/device_sync",
+  "+chrome/browser/ash/multidevice_setup",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/multidevice_setup/DEPS b/chrome/browser/ash/multidevice_setup/DEPS
new file mode 100644
index 0000000..dae7995
--- /dev/null
+++ b/chrome/browser/ash/multidevice_setup/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/multidevice_setup",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/android_sms",
+  "+chrome/browser/ash/cryptauth",
+  "+chrome/browser/ash/device_sync",
+  "+chrome/browser/ash/login/quick_unlock",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/nearby/DEPS b/chrome/browser/ash/nearby/DEPS
index 9931f45..1461946 100644
--- a/chrome/browser/ash/nearby/DEPS
+++ b/chrome/browser/ash/nearby/DEPS
@@ -1,5 +1,29 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/nearby",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_features.h",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/nearby_sharing",
+  "+chrome/browser/profiles",
+  "+chrome/browser/push_notification",
+  "+chrome/browser/signin",
   "+chrome/services/sharing/nearby/test_support",
+
+  # Dependencies outside of //chrome:
   "+components/cross_device/logging",
   "+components/push_notification",
 ]
diff --git a/chrome/browser/ash/nearby/fake_quick_start_connectivity_service.cc b/chrome/browser/ash/nearby/fake_quick_start_connectivity_service.cc
index b0e45ecb..448ef13 100644
--- a/chrome/browser/ash/nearby/fake_quick_start_connectivity_service.cc
+++ b/chrome/browser/ash/nearby/fake_quick_start_connectivity_service.cc
@@ -6,7 +6,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/components/quick_start/fake_quick_start_decoder.h"
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom.h"
 #include "mojo/public/cpp/bindings/shared_remote.h"
diff --git a/chrome/browser/ash/nearby/fake_quick_start_connectivity_service.h b/chrome/browser/ash/nearby/fake_quick_start_connectivity_service.h
index 9177ec0..b097f2b 100644
--- a/chrome/browser/ash/nearby/fake_quick_start_connectivity_service.h
+++ b/chrome/browser/ash/nearby/fake_quick_start_connectivity_service.h
@@ -10,7 +10,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ash/nearby/quick_start_connectivity_service.h"
-#include "chrome/browser/nearby_sharing/public/cpp/fake_nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/fake_nearby_connections_manager.h"
 #include "chromeos/ash/components/quick_start/fake_quick_start_decoder.h"
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom.h"
 #include "mojo/public/cpp/bindings/shared_remote.h"
diff --git a/chrome/browser/ash/nearby/presence/DEPS b/chrome/browser/ash/nearby/presence/DEPS
new file mode 100644
index 0000000..2f041a5
--- /dev/null
+++ b/chrome/browser/ash/nearby/presence/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/nearby/presence",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/nearby",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_features.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/push_notification",
+  "+chrome/browser/signin",
+]
diff --git a/chrome/browser/ash/nearby/quick_start_connectivity_service_impl.cc b/chrome/browser/ash/nearby/quick_start_connectivity_service_impl.cc
index 0822035..19ad25f 100644
--- a/chrome/browser/ash/nearby/quick_start_connectivity_service_impl.cc
+++ b/chrome/browser/ash/nearby/quick_start_connectivity_service_impl.cc
@@ -7,7 +7,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/nearby_sharing/nearby_connections_manager_impl.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/services/nearby/public/cpp/nearby_process_manager.h"
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom.h"
 #include "mojo/public/cpp/bindings/shared_remote.h"
diff --git a/chrome/browser/ash/net/DEPS b/chrome/browser/ash/net/DEPS
index 2a94e1b..2af8b97c 100644
--- a/chrome/browser/ash/net/DEPS
+++ b/chrome/browser/ash/net/DEPS
@@ -1,3 +1,47 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/net",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/notifications",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/affiliation",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/extensions/api/settings_private",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sync",
+  "+chrome/browser/ui/ash/network",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/login",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   "system_proxy_manager_unittest\.cc": [
     "+services/network/network_context.h"
diff --git a/chrome/browser/ash/net/dns_over_https/DEPS b/chrome/browser/ash/net/dns_over_https/DEPS
new file mode 100644
index 0000000..dfd971c
--- /dev/null
+++ b/chrome/browser/ash/net/dns_over_https/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/net/dns_over_https",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/net",
+  "+chrome/common/pref_names.h",
+]
diff --git a/chrome/browser/ash/net/network_diagnostics/DEPS b/chrome/browser/ash/net/network_diagnostics/DEPS
new file mode 100644
index 0000000..0ba9e57
--- /dev/null
+++ b/chrome/browser/ash/net/network_diagnostics/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/net/network_diagnostics",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/net/network_health/DEPS b/chrome/browser/ash/net/network_health/DEPS
new file mode 100644
index 0000000..8e29daf
--- /dev/null
+++ b/chrome/browser/ash/net/network_health/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/net/network_health",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/net/network_diagnostics",
+]
diff --git a/chrome/browser/ash/net/rollback_network_config/DEPS b/chrome/browser/ash/net/rollback_network_config/DEPS
new file mode 100644
index 0000000..d9e6a27
--- /dev/null
+++ b/chrome/browser/ash/net/rollback_network_config/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/net/rollback_network_config",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/notifications/DEPS b/chrome/browser/ash/notifications/DEPS
index b6ef0f3..7415ac47 100644
--- a/chrome/browser/ash/notifications/DEPS
+++ b/chrome/browser/ash/notifications/DEPS
@@ -1,3 +1,42 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/notifications",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/handlers",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/test_extension_system.h",
+  "+chrome/browser/media/webrtc",
+  "+chrome/browser/notifications",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_dialogs.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/screen_capture_notification_ui.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+ui/message_center",
 ]
\ No newline at end of file
diff --git a/chrome/browser/ash/os_feedback/DEPS b/chrome/browser/ash/os_feedback/DEPS
new file mode 100644
index 0000000..230236d4
--- /dev/null
+++ b/chrome/browser/ash/os_feedback/DEPS
@@ -0,0 +1,34 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/os_feedback",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/android",
+  "+chrome/browser/ash/multidevice_setup",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/feedback",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/ash/system_web_apps",
+  "+chrome/browser/ui/browser_dialogs.h",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/webui/ash",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/ownership/DEPS b/chrome/browser/ash/ownership/DEPS
new file mode 100644
index 0000000..e22c5aa
--- /dev/null
+++ b/chrome/browser/ash/ownership/DEPS
@@ -0,0 +1,28 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/ownership",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/net",
+  "+chrome/browser/profiles",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/passkeys/DEPS b/chrome/browser/ash/passkeys/DEPS
index 0d9c379..2dd2c48 100644
--- a/chrome/browser/ash/passkeys/DEPS
+++ b/chrome/browser/ash/passkeys/DEPS
@@ -1,3 +1,24 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/passkeys",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/trusted_vault",
+  "+chrome/browser/webauthn",
+
+  # Dependencies outside of //chrome:
   "+device/fido",
 ]
diff --git a/chrome/browser/ash/pcie_peripheral/DEPS b/chrome/browser/ash/pcie_peripheral/DEPS
new file mode 100644
index 0000000..0075d700
--- /dev/null
+++ b/chrome/browser/ash/pcie_peripheral/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/pcie_peripheral",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/phonehub/DEPS b/chrome/browser/ash/phonehub/DEPS
index 2e8b2c4..ee1c27b 100644
--- a/chrome/browser/ash/phonehub/DEPS
+++ b/chrome/browser/ash/phonehub/DEPS
@@ -1,4 +1,37 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/phonehub",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/attestation",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/device_sync",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/multidevice_setup",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/secure_channel",
+  "+chrome/browser/ash/sync",
+  "+chrome/browser/download",
+  "+chrome/browser/favicon",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sync",
+  "+chrome/browser/ui/ash/holding_space",
+  "+chrome/browser/ui/webui/ash/multidevice_setup",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+components/favicon",
   "+components/sessions/core",
   "+components/sync_sessions",
diff --git a/chrome/browser/ash/platform_keys/DEPS b/chrome/browser/ash/platform_keys/DEPS
new file mode 100644
index 0000000..2b50b514
--- /dev/null
+++ b/chrome/browser/ash/platform_keys/DEPS
@@ -0,0 +1,29 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/platform_keys",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/certificate_provider",
+  "+chrome/browser/chromeos/platform_keys",
+  "+chrome/browser/extensions/api/enterprise_platform_keys",
+  "+chrome/browser/net",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/platform_keys/key_permissions/DEPS b/chrome/browser/ash/platform_keys/key_permissions/DEPS
new file mode 100644
index 0000000..61c3be81
--- /dev/null
+++ b/chrome/browser/ash/platform_keys/key_permissions/DEPS
@@ -0,0 +1,24 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/platform_keys/key_permissions",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/platform_keys",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/plugin_vm/DEPS b/chrome/browser/ash/plugin_vm/DEPS
new file mode 100644
index 0000000..79322e7
--- /dev/null
+++ b/chrome/browser/ash/plugin_vm/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/plugin_vm",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/download",
+  "+chrome/browser/notifications",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/browser/ui/simple_message_box.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/DEPS b/chrome/browser/ash/policy/DEPS
index 0a8653e..e90a816f 100644
--- a/chrome/browser/ash/policy/DEPS
+++ b/chrome/browser/ash/policy/DEPS
@@ -1,4 +1,99 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chrome_content_browser_client.h",
+  "+chrome/browser/chromeos/extensions",
+  "+chrome/browser/chromeos/policy/dlp",
+  "+chrome/browser/chromeos/reporting",
+  "+chrome/browser/crash_upload_list",
+  "+chrome/browser/device_identity",
+  "+chrome/browser/enterprise/connectors",
+  "+chrome/browser/enterprise/data_controls",
+  "+chrome/browser/enterprise/reporting",
+  "+chrome/browser/enterprise/util",
+  "+chrome/browser/extensions/api/file_system",
+  "+chrome/browser/extensions/api/tab_capture",
+  "+chrome/browser/extensions/browsertest_util.h",
+  "+chrome/browser/extensions/chrome_test_extension_loader.h",
+  "+chrome/browser/extensions/crx_installer.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/external_loader.h",
+  "+chrome/browser/extensions/external_provider_impl.h",
+  "+chrome/browser/extensions/install_observer.h",
+  "+chrome/browser/extensions/install_tracker.h",
+  "+chrome/browser/extensions/policy_handlers.h",
+  "+chrome/browser/extensions/policy_test_utils.h",
+  "+chrome/browser/extensions/updater",
+  "+chrome/browser/file_select_helper.h",
+  "+chrome/browser/invalidation",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/media",
+  "+chrome/browser/metrics",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sessions",
+  "+chrome/browser/signin",
+  "+chrome/browser/site_isolation",
+  "+chrome/browser/support_tool",
+  "+chrome/browser/sync",
+  "+chrome/browser/tracing",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_list_observer.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/web_applications/test",
+  "+chrome/browser/ui/webui/ash/cloud_upload",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/browser/ui/webui/ash/settings/pages/storage",
+  "+chrome/browser/ui/webui/certificates_handler.h",
+  "+chrome/browser/ui/webui/support_tool",
+  "+chrome/browser/unified_consent",
+  "+chrome/browser/upgrade_detector",
+  "+chrome/browser/web_applications/isolated_web_apps",
+  "+chrome/browser/web_applications/mojom",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/user_display_mode.h",
+  "+chrome/browser/web_applications/web_app.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_provider_factory.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_registrar.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_content_client.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+
   # Run
   #
   #   buildtools/checkdeps/checkdeps.py chrome/browser/ash/policy
diff --git a/chrome/browser/ash/policy/affiliation/DEPS b/chrome/browser/ash/policy/affiliation/DEPS
new file mode 100644
index 0000000..9ff599a
--- /dev/null
+++ b/chrome/browser/ash/policy/affiliation/DEPS
@@ -0,0 +1,24 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/affiliation",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/net",
+  "+chrome/browser/profiles",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/arc/DEPS b/chrome/browser/ash/policy/arc/DEPS
new file mode 100644
index 0000000..97a9946
--- /dev/null
+++ b/chrome/browser/ash/policy/arc/DEPS
@@ -0,0 +1,25 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/arc",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/login/ui",
+  "+chrome/browser/ash/policy/affiliation",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/policy/core/DEPS b/chrome/browser/ash/policy/core/DEPS
new file mode 100644
index 0000000..0d4968a
--- /dev/null
+++ b/chrome/browser/ash/policy/core/DEPS
@@ -0,0 +1,59 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/core",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chromeos/extensions",
+  "+chrome/browser/device_identity",
+  "+chrome/browser/enterprise/reporting",
+  "+chrome/browser/extensions/chrome_test_extension_loader.h",
+  "+chrome/browser/extensions/crx_installer.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/external_loader.h",
+  "+chrome/browser/extensions/external_provider_impl.h",
+  "+chrome/browser/extensions/policy_handlers.h",
+  "+chrome/browser/extensions/updater",
+  "+chrome/browser/invalidation",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/metrics",
+  "+chrome/browser/net",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_list_observer.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/browser/ui/webui/certificates_handler.h",
+  "+chrome/browser/unified_consent",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_content_client.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/dev_mode/DEPS b/chrome/browser/ash/policy/dev_mode/DEPS
new file mode 100644
index 0000000..964ac87
--- /dev/null
+++ b/chrome/browser/ash/policy/dev_mode/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/dev_mode",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/policy/display/DEPS b/chrome/browser/ash/policy/display/DEPS
new file mode 100644
index 0000000..68c8c96
--- /dev/null
+++ b/chrome/browser/ash/policy/display/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/display",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/login/ui",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/lifetime",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/dlp/DEPS b/chrome/browser/ash/policy/dlp/DEPS
new file mode 100644
index 0000000..33323cf
--- /dev/null
+++ b/chrome/browser/ash/policy/dlp/DEPS
@@ -0,0 +1,53 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/dlp",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/extensions/file_manager",
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/file_system_provider",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/login",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/policy/dlp",
+  "+chrome/browser/enterprise/connectors",
+  "+chrome/browser/enterprise/data_controls",
+  "+chrome/browser/extensions/api/file_system",
+  "+chrome/browser/extensions/api/tab_capture",
+  "+chrome/browser/file_select_helper.h",
+  "+chrome/browser/media",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy/messaging_layer/public",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_list_observer.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/webui/ash/cloud_upload",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/extensions",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/enrollment/DEPS b/chrome/browser/ash/policy/enrollment/DEPS
new file mode 100644
index 0000000..a7ad630b
--- /dev/null
+++ b/chrome/browser/ash/policy/enrollment/DEPS
@@ -0,0 +1,33 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/enrollment",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/dev_mode",
+  "+chrome/browser/ash/policy/server_backed_state",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/enterprise/util",
+  "+chrome/browser/net",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/common/chrome_content_client.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/external_data/DEPS b/chrome/browser/ash/policy/external_data/DEPS
new file mode 100644
index 0000000..9fe8c79
--- /dev/null
+++ b/chrome/browser/ash/policy/external_data/DEPS
@@ -0,0 +1,35 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/external_data",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/login/users/avatar",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/handlers",
+  "+chrome/browser/ash/policy/invalidation",
+  "+chrome/browser/ash/policy/login",
+  "+chrome/browser/ash/printing",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/fuzzer/DEPS b/chrome/browser/ash/policy/fuzzer/DEPS
new file mode 100644
index 0000000..c4bcb60
--- /dev/null
+++ b/chrome/browser/ash/policy/fuzzer/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/fuzzer",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/dbus",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/policy",
+  "+chrome/common/chrome_paths.h",
+]
diff --git a/chrome/browser/ash/policy/handlers/DEPS b/chrome/browser/ash/policy/handlers/DEPS
new file mode 100644
index 0000000..b95043e
--- /dev/null
+++ b/chrome/browser/ash/policy/handlers/DEPS
@@ -0,0 +1,42 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/handlers",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/extensions/policy_handlers.h",
+  "+chrome/browser/extensions/updater",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/media/webrtc",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/site_isolation",
+  "+chrome/browser/tracing",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/browser/upgrade_detector",
+  "+chrome/browser/web_applications/isolated_web_apps",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/handlers/device_system_wide_tracing_enabled_browsertest.cc b/chrome/browser/ash/policy/handlers/device_system_wide_tracing_enabled_browsertest.cc
index f3ad50afb..0748fec 100644
--- a/chrome/browser/ash/policy/handlers/device_system_wide_tracing_enabled_browsertest.cc
+++ b/chrome/browser/ash/policy/handlers/device_system_wide_tracing_enabled_browsertest.cc
@@ -76,8 +76,7 @@
 // device.
 IN_PROC_BROWSER_TEST_F(DeviceSystemWideTracingEnabledPolicyConsumerOwnedTest,
                        DefaultEnabled) {
-  auto tracing_delegate = std::make_unique<ChromeTracingDelegate>();
-  ASSERT_TRUE(tracing_delegate->IsSystemWideTracingEnabled());
+  ASSERT_TRUE(ChromeTracingDelegate::IsSystemWideTracingEnabled());
 }
 
 class DeviceSystemWideTracingEnabledPolicyEnterpriseManagedTest
@@ -98,16 +97,15 @@
 IN_PROC_BROWSER_TEST_F(
     DeviceSystemWideTracingEnabledPolicyEnterpriseManagedTest,
     PolicyApplied) {
-  auto tracing_delegate = std::make_unique<ChromeTracingDelegate>();
-  ASSERT_FALSE(tracing_delegate->IsSystemWideTracingEnabled());
+  ASSERT_FALSE(ChromeTracingDelegate::IsSystemWideTracingEnabled());
 
   UpdatePolicy(true);
   SyncRefreshDevicePolicy();
-  ASSERT_TRUE(tracing_delegate->IsSystemWideTracingEnabled());
+  ASSERT_TRUE(ChromeTracingDelegate::IsSystemWideTracingEnabled());
 
   UpdatePolicy(false);
   SyncRefreshDevicePolicy();
-  ASSERT_FALSE(tracing_delegate->IsSystemWideTracingEnabled());
+  ASSERT_FALSE(ChromeTracingDelegate::IsSystemWideTracingEnabled());
 }
 
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/invalidation/DEPS b/chrome/browser/ash/policy/invalidation/DEPS
new file mode 100644
index 0000000..8af94c7
--- /dev/null
+++ b/chrome/browser/ash/policy/invalidation/DEPS
@@ -0,0 +1,28 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/invalidation",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/device_identity",
+  "+chrome/browser/invalidation",
+  "+chrome/browser/net",
+  "+chrome/browser/policy/cloud",
+  "+chrome/browser/profiles",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/local_user_files/DEPS b/chrome/browser/ash/policy/local_user_files/DEPS
new file mode 100644
index 0000000..21fa8891
--- /dev/null
+++ b/chrome/browser/ash/policy/local_user_files/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/local_user_files",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/file_system_provider",
+  "+chrome/browser/ash/policy/handlers",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/extensions/login_screen/login/cleanup",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/webui/ash/cloud_upload",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/login/DEPS b/chrome/browser/ash/policy/login/DEPS
new file mode 100644
index 0000000..04776a46
--- /dev/null
+++ b/chrome/browser/ash/policy/login/DEPS
@@ -0,0 +1,41 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/login",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/accessibility",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/test_support",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/extensions/crx_installer.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/install_observer.h",
+  "+chrome/browser/extensions/install_tracker.h",
+  "+chrome/browser/extensions/policy_test_utils.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/net",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/keyboard",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/networking/DEPS b/chrome/browser/ash/policy/networking/DEPS
new file mode 100644
index 0000000..3463551
--- /dev/null
+++ b/chrome/browser/ash/policy/networking/DEPS
@@ -0,0 +1,29 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/networking",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/net",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/network",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/off_hours/DEPS b/chrome/browser/ash/policy/off_hours/DEPS
new file mode 100644
index 0000000..27c3716
--- /dev/null
+++ b/chrome/browser/ash/policy/off_hours/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/off_hours",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/settings",
+]
diff --git a/chrome/browser/ash/policy/remote_commands/DEPS b/chrome/browser/ash/policy/remote_commands/DEPS
new file mode 100644
index 0000000..244bcf53
--- /dev/null
+++ b/chrome/browser/ash/policy/remote_commands/DEPS
@@ -0,0 +1,44 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/remote_commands",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_mode",
+  "+chrome/browser/ash/arc/policy",
+  "+chrome/browser/ash/arc/session",
+  "+chrome/browser/ash/arc/test",
+  "+chrome/browser/ash/attestation",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/invalidation",
+  "+chrome/browser/ash/policy/scheduled_task_handler",
+  "+chrome/browser/ash/policy/test_support",
+  "+chrome/browser/ash/policy/uploading",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/ash/system",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/device_identity",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy/messaging_layer/proto/synced",
+  "+chrome/browser/policy/messaging_layer/public",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/support_tool",
+  "+chrome/browser/ui/webui/support_tool",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/reporting/DEPS b/chrome/browser/ash/policy/reporting/DEPS
new file mode 100644
index 0000000..841bc8e
--- /dev/null
+++ b/chrome/browser/ash/policy/reporting/DEPS
@@ -0,0 +1,54 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/reporting",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/login/app_mode",
+  "+chrome/browser/ash/login/lock",
+  "+chrome/browser/ash/login/session",
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/net/network_diagnostics",
+  "+chrome/browser/ash/net/network_health",
+  "+chrome/browser/ash/policy/affiliation",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/status_collector",
+  "+chrome/browser/ash/policy/test_support",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/chromeos/reporting",
+  "+chrome/browser/extensions/browsertest_util.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sessions",
+  "+chrome/browser/sync",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/web_applications/test",
+  "+chrome/browser/upgrade_detector",
+  "+chrome/browser/web_applications/mojom",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/user_display_mode.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/rsu/DEPS b/chrome/browser/ash/policy/rsu/DEPS
new file mode 100644
index 0000000..48cc38a
--- /dev/null
+++ b/chrome/browser/ash/policy/rsu/DEPS
@@ -0,0 +1,21 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/rsu",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/attestation",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/settings",
+  "+chrome/common/pref_names.h",
+]
diff --git a/chrome/browser/ash/policy/scheduled_task_handler/DEPS b/chrome/browser/ash/policy/scheduled_task_handler/DEPS
new file mode 100644
index 0000000..22a722d2
--- /dev/null
+++ b/chrome/browser/ash/policy/scheduled_task_handler/DEPS
@@ -0,0 +1,26 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/scheduled_task_handler",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/app_restore",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/notifications",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/device_scheduled_reboot",
+  "+chrome/common/chrome_features.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/server_backed_state/DEPS b/chrome/browser/ash/policy/server_backed_state/DEPS
new file mode 100644
index 0000000..a43f770
--- /dev/null
+++ b/chrome/browser/ash/policy/server_backed_state/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/server_backed_state",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/common/pref_names.h",
+]
diff --git a/chrome/browser/ash/policy/status_collector/DEPS b/chrome/browser/ash/policy/status_collector/DEPS
new file mode 100644
index 0000000..62765587
--- /dev/null
+++ b/chrome/browser/ash/policy/status_collector/DEPS
@@ -0,0 +1,45 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/status_collector",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_mode",
+  "+chrome/browser/ash/child_accounts",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/ownership",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chrome_content_browser_client.h",
+  "+chrome/browser/crash_upload_list",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/webui/ash/settings/pages/storage",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app.h",
+  "+chrome/browser/web_applications/web_app_provider_factory.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_registrar.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_content_client.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/test_support/DEPS b/chrome/browser/ash/policy/test_support/DEPS
new file mode 100644
index 0000000..2421dbe
--- /dev/null
+++ b/chrome/browser/ash/policy/test_support/DEPS
@@ -0,0 +1,25 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/test_support",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/enrollment",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/enrollment",
+  "+chrome/browser/ash/policy/server_backed_state",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/tools/DEPS b/chrome/browser/ash/policy/tools/DEPS
new file mode 100644
index 0000000..cf2fc67
--- /dev/null
+++ b/chrome/browser/ash/policy/tools/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/tools",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/policy/uploading/DEPS b/chrome/browser/ash/policy/uploading/DEPS
new file mode 100644
index 0000000..f1c0e2e
--- /dev/null
+++ b/chrome/browser/ash/policy/uploading/DEPS
@@ -0,0 +1,30 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/uploading",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/status_collector",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/device_identity",
+  "+chrome/browser/media/webrtc",
+  "+chrome/browser/policy",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/extensions",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/policy/value_validation/DEPS b/chrome/browser/ash/policy/value_validation/DEPS
new file mode 100644
index 0000000..1c595205
--- /dev/null
+++ b/chrome/browser/ash/policy/value_validation/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/policy/value_validation",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/power/DEPS b/chrome/browser/ash/power/DEPS
new file mode 100644
index 0000000..4a48abd
--- /dev/null
+++ b/chrome/browser/ash/power/DEPS
@@ -0,0 +1,38 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/power",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/accessibility",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/login/demo_mode",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/test_extension_system.h",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/tab_contents",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/extensions/api",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/power/auto_screen_brightness/DEPS b/chrome/browser/ash/power/auto_screen_brightness/DEPS
new file mode 100644
index 0000000..5b4f1544
--- /dev/null
+++ b/chrome/browser/ash/power/auto_screen_brightness/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/power/auto_screen_brightness",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/power/ml/DEPS b/chrome/browser/ash/power/ml/DEPS
new file mode 100644
index 0000000..0c19c5b6
--- /dev/null
+++ b/chrome/browser/ash/power/ml/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/power/ml",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/accessibility",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/profiles",
+  "+chrome/browser/tab_contents",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/common/chrome_features.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/power/smart_charging/DEPS b/chrome/browser/ash/power/smart_charging/DEPS
new file mode 100644
index 0000000..8ca0ee3
--- /dev/null
+++ b/chrome/browser/ash/power/smart_charging/DEPS
@@ -0,0 +1,21 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/power/smart_charging",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/power/ml",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/tabs",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/printing/DEPS b/chrome/browser/ash/printing/DEPS
index ddb3487..52c41c19 100644
--- a/chrome/browser/ash/printing/DEPS
+++ b/chrome/browser/ash/printing/DEPS
@@ -1,3 +1,58 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/printing",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/scanning",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/ash/system_web_apps/test_support",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/chromeos/printing",
+  "+chrome/browser/component_updater",
+  "+chrome/browser/history",
+  "+chrome/browser/local_discovery",
+  "+chrome/browser/net",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/printing",
+  "+chrome/browser/profiles",
+  "+chrome/browser/scalable_iph",
+  "+chrome/browser/sync",
+  "+chrome/browser/ui/ash/system_web_apps",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/chrome_web_modal_dialog_manager_delegate.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/web_applications/test",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/services/cups_proxy",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   "(server_printers_fetcher|server_printers_provider_unittest)\.cc": [
     # IPP protocol; it is needed for communication with print servers.
diff --git a/chrome/browser/ash/printing/history/DEPS b/chrome/browser/ash/printing/history/DEPS
new file mode 100644
index 0000000..3e08e1c
--- /dev/null
+++ b/chrome/browser/ash/printing/history/DEPS
@@ -0,0 +1,25 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/printing/history",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/printing",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/chromeos/printing",
+  "+chrome/browser/printing",
+  "+chrome/browser/profiles",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/printing/oauth2/DEPS b/chrome/browser/ash/printing/oauth2/DEPS
new file mode 100644
index 0000000..a99f89be
--- /dev/null
+++ b/chrome/browser/ash/printing/oauth2/DEPS
@@ -0,0 +1,24 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/printing/oauth2",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sync",
+  "+chrome/browser/ui/chrome_web_modal_dialog_manager_delegate.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/printing/print_management/DEPS b/chrome/browser/ash/printing/print_management/DEPS
new file mode 100644
index 0000000..05ce32b
--- /dev/null
+++ b/chrome/browser/ash/printing/print_management/DEPS
@@ -0,0 +1,32 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/printing/print_management",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/printing",
+  "+chrome/browser/ash/system_web_apps/test_support",
+  "+chrome/browser/chromeos/printing",
+  "+chrome/browser/history",
+  "+chrome/browser/printing",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/system_web_apps",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/web_applications/test",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/privacy_hub/DEPS b/chrome/browser/ash/privacy_hub/DEPS
new file mode 100644
index 0000000..1f3582a
--- /dev/null
+++ b/chrome/browser/ash/privacy_hub/DEPS
@@ -0,0 +1,24 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/privacy_hub",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+  "+chrome/browser/notifications",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/profiles/DEPS b/chrome/browser/ash/profiles/DEPS
new file mode 100644
index 0000000..bf8bd55
--- /dev/null
+++ b/chrome/browser/ash/profiles/DEPS
@@ -0,0 +1,27 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/profiles",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/browsing_data",
+  "+chrome/browser/extensions/component_loader.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_switches.h",
+]
diff --git a/chrome/browser/ash/quick_pair/DEPS b/chrome/browser/ash/quick_pair/DEPS
new file mode 100644
index 0000000..ed152ae
--- /dev/null
+++ b/chrome/browser/ash/quick_pair/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/quick_pair",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list/arc",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/image_fetcher",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+]
diff --git a/chrome/browser/ash/release_notes/DEPS b/chrome/browser/ash/release_notes/DEPS
new file mode 100644
index 0000000..167b5b01
--- /dev/null
+++ b/chrome/browser/ash/release_notes/DEPS
@@ -0,0 +1,28 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/release_notes",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/notifications",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/remote_apps/DEPS b/chrome/browser/ash/remote_apps/DEPS
new file mode 100644
index 0000000..bbb361d
--- /dev/null
+++ b/chrome/browser/ash/remote_apps/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/remote_apps",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/policy/core",
+  "+chrome/browser/ash/policy/test_support",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/extensions/chrome_test_extension_loader.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/app_list",
+  "+chrome/browser/ui/ash/shelf",
+  "+chrome/common/apps/platform_apps/api",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+]
diff --git a/chrome/browser/ash/scalable_iph/DEPS b/chrome/browser/ash/scalable_iph/DEPS
index 951c32e..59842fb 100644
--- a/chrome/browser/ash/scalable_iph/DEPS
+++ b/chrome/browser/ash/scalable_iph/DEPS
@@ -1,4 +1,45 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/scalable_iph",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/app_list",
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/phonehub",
+  "+chrome/browser/ash/printing",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/feature_engagement",
+  "+chrome/browser/profiles",
+  "+chrome/browser/scalable_iph",
+  "+chrome/browser/ui/ash/multi_user",
+  "+chrome/browser/ui/ash/system_web_apps",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_navigator.h",
+  "+chrome/browser/ui/browser_navigator_params.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+ui/message_center/message_center.h",
   "+ui/message_center/message_center_observer.h",
 ]
diff --git a/chrome/browser/ash/scanning/DEPS b/chrome/browser/ash/scanning/DEPS
index 840efa49..edfdff8 100644
--- a/chrome/browser/ash/scanning/DEPS
+++ b/chrome/browser/ash/scanning/DEPS
@@ -1,3 +1,39 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/scanning",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/login/test",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/local_discovery",
+  "+chrome/browser/notifications",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/chrome_select_file_policy.h",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+  "+chrome/test/interaction",
+]
+
 specific_include_rules = {
   "scan_service\.h": [
     "+services/device/wake_lock/power_save_blocker/power_save_blocker.h",
diff --git a/chrome/browser/ash/secure_channel/DEPS b/chrome/browser/ash/secure_channel/DEPS
new file mode 100644
index 0000000..2be811b1
--- /dev/null
+++ b/chrome/browser/ash/secure_channel/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/secure_channel",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/nearby",
+  "+chrome/browser/profiles",
+]
diff --git a/chrome/browser/ash/secure_channel/util/DEPS b/chrome/browser/ash/secure_channel/util/DEPS
new file mode 100644
index 0000000..5fca52f2
--- /dev/null
+++ b/chrome/browser/ash/secure_channel/util/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/secure_channel/util",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/settings/DEPS b/chrome/browser/ash/settings/DEPS
new file mode 100644
index 0000000..5db5fcb
--- /dev/null
+++ b/chrome/browser/ash/settings/DEPS
@@ -0,0 +1,26 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/settings",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/about_flags.h",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/net",
+  "+chrome/browser/profiles",
+  "+chrome/browser/site_isolation",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/sharesheet/DEPS b/chrome/browser/ash/sharesheet/DEPS
new file mode 100644
index 0000000..5cf7335
--- /dev/null
+++ b/chrome/browser/ash/sharesheet/DEPS
@@ -0,0 +1,26 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/sharesheet",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app/vector_icons",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/fusebox",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sharesheet",
+  "+chrome/browser/ui/ash/sharesheet",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/shimless_rma/DEPS b/chrome/browser/ash/shimless_rma/DEPS
index fec8827..c4b6452 100644
--- a/chrome/browser/ash/shimless_rma/DEPS
+++ b/chrome/browser/ash/shimless_rma/DEPS
@@ -1,3 +1,39 @@
 include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/shimless_rma",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/accessibility",
+  "+chrome/browser/ash/login",
+  "+chrome/browser/ash/system",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/extensions/crx_installer.h",
+  "+chrome/browser/extensions/extension_garbage_collector_factory.h",
+  "+chrome/browser/extensions/extension_service.h",
+  "+chrome/browser/extensions/extension_service_test_base.h",
+  "+chrome/browser/extensions/test_extension_system.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/webui/ash",
+  "+chrome/browser/web_applications/isolated_web_apps",
+  "+chrome/browser/web_applications/web_app_command_scheduler.h",
+  "+chrome/browser/web_applications/web_app.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/common/chromeos/extensions",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+
+  # Dependencies outside of //chrome:
   "+components/web_package",
 ]
diff --git a/chrome/browser/ash/smart_reader/DEPS b/chrome/browser/ash/smart_reader/DEPS
new file mode 100644
index 0000000..3fe3c912
--- /dev/null
+++ b/chrome/browser/ash/smart_reader/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/smart_reader",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/smb_client/DEPS b/chrome/browser/ash/smb_client/DEPS
new file mode 100644
index 0000000..1f6c82c
--- /dev/null
+++ b/chrome/browser/ash/smb_client/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/smb_client",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/file_system_provider",
+  "+chrome/browser/ash/kerberos",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/webui/ash/smb_shares",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/smb_client/discovery/DEPS b/chrome/browser/ash/smb_client/discovery/DEPS
new file mode 100644
index 0000000..db92ae89
--- /dev/null
+++ b/chrome/browser/ash/smb_client/discovery/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/smb_client/discovery",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/smb_client",
+]
diff --git a/chrome/browser/ash/smb_client/fileapi/DEPS b/chrome/browser/ash/smb_client/fileapi/DEPS
new file mode 100644
index 0000000..d9a54019
--- /dev/null
+++ b/chrome/browser/ash/smb_client/fileapi/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/smb_client/fileapi",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/fileapi",
+  "+chrome/browser/ash/smb_client",
+]
diff --git a/chrome/browser/ash/smb_client/fuzzer_data/DEPS b/chrome/browser/ash/smb_client/fuzzer_data/DEPS
new file mode 100644
index 0000000..6cf8150
--- /dev/null
+++ b/chrome/browser/ash/smb_client/fuzzer_data/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/smb_client/fuzzer_data",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/sync/DEPS b/chrome/browser/ash/sync/DEPS
new file mode 100644
index 0000000..7c77199
--- /dev/null
+++ b/chrome/browser/ash/sync/DEPS
@@ -0,0 +1,31 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/sync",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/notifications",
+  "+chrome/browser/profiles",
+  "+chrome/browser/sync",
+  "+chrome/browser/ui/ash/multi_user",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/scoped_tabbed_browser_displayer.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/webui/signin",
+  "+chrome/common/url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/system/DEPS b/chrome/browser/ash/system/DEPS
new file mode 100644
index 0000000..701e47a
--- /dev/null
+++ b/chrome/browser/ash/system/DEPS
@@ -0,0 +1,29 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/system",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/browser_process_platform_part_ash.h",
+  "+chrome/browser/browser_process_platform_part.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/webui/ash/login",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/system_logs/DEPS b/chrome/browser/ash/system_logs/DEPS
index 13ad865e..f75797e 100644
--- a/chrome/browser/ash/system_logs/DEPS
+++ b/chrome/browser/ash/system_logs/DEPS
@@ -1,3 +1,34 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/system_logs",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/net/network_health",
+  "+chrome/browser/ash/os_feedback",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/feedback/system_logs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/logging_chrome.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   "touch_log_source\.cc": [
     "+ash/touch/touch_hud_debug.h",
diff --git a/chrome/browser/ash/system_logs/virtual_keyboard_log_source.cc b/chrome/browser/ash/system_logs/virtual_keyboard_log_source.cc
index 86ba9301..fa9300f 100644
--- a/chrome/browser/ash/system_logs/virtual_keyboard_log_source.cc
+++ b/chrome/browser/ash/system_logs/virtual_keyboard_log_source.cc
@@ -68,8 +68,12 @@
               "\n";
 
   int external_keyboard_count = 1;
-  for (const ui::InputDevice& device :
-       virtual_keyboard_controller->GetExternalKeyboards()) {
+  std::vector<ui::InputDevice> external_keyboards =
+      virtual_keyboard_controller->GetExternalKeyboards();
+  if (external_keyboards.size() == 0) {
+    log_data += "No External Keyboard Detected\n";
+  }
+  for (const ui::InputDevice& device : external_keyboards) {
     const std::string external_keyboard_count_converted =
         base::NumberToString(external_keyboard_count);
     log_data +=
diff --git a/chrome/browser/ash/system_web_apps/DEPS b/chrome/browser/ash/system_web_apps/DEPS
new file mode 100644
index 0000000..98751ea
--- /dev/null
+++ b/chrome/browser/ash/system_web_apps/DEPS
@@ -0,0 +1,94 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/system_web_apps",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/app",
+  "+chrome/browser/accessibility/media_app",
+  "+chrome/browser/apps/almanac_api_client",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/upload_office_to_cloud",
+  "+chrome/browser/devtools",
+  "+chrome/browser/error_reporting",
+  "+chrome/browser/extensions/component_loader.h",
+  "+chrome/browser/extensions/extension_tab_util.h",
+  "+chrome/browser/file_system_access",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/media/webrtc",
+  "+chrome/browser/metrics",
+  "+chrome/browser/notifications",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/renderer_context_menu",
+  "+chrome/browser/scalable_iph",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_list_observer.h",
+  "+chrome/browser/ui/browser_navigator.h",
+  "+chrome/browser/ui/browser_navigator_params.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/browser/ui/webui/ash",
+  "+chrome/browser/ui/webui/chrome_web_ui_controller_factory.h",
+  "+chrome/browser/ui/webui/plural_string_handler.h",
+  "+chrome/browser/ui/webui/sanitized_image_source.h",
+  "+chrome/browser/ui/webui/webui_util.h",
+  "+chrome/browser/web_applications/extension_status_utils.h",
+  "+chrome/browser/web_applications/external_install_options.h",
+  "+chrome/browser/web_applications/externally_managed_app_manager.h",
+  "+chrome/browser/web_applications/manifest_update_manager.h",
+  "+chrome/browser/web_applications/mojom",
+  "+chrome/browser/web_applications/os_integration",
+  "+chrome/browser/web_applications/policy",
+  "+chrome/browser/web_applications/proto",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_command_manager.h",
+  "+chrome/browser/web_applications/web_app_command_scheduler.h",
+  "+chrome/browser/web_applications/web_app_constants.h",
+  "+chrome/browser/web_applications/web_app.h",
+  "+chrome/browser/web_applications/web_app_helpers.h",
+  "+chrome/browser/web_applications/web_app_icon_generator.h",
+  "+chrome/browser/web_applications/web_app_icon_manager.h",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_launch_queue.h",
+  "+chrome/browser/web_applications/web_app_provider_factory.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_registrar.h",
+  "+chrome/browser/web_applications/web_app_registry_update.h",
+  "+chrome/browser/web_applications/web_app_system_web_app_delegate_map_utils.h",
+  "+chrome/browser/web_applications/web_app_tab_helper.h",
+  "+chrome/browser/web_applications/web_app_ui_manager.h",
+  "+chrome/browser/web_applications/web_app_utils.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_features.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/url_constants.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+  "+chrome/test/interaction",
+]
diff --git a/chrome/browser/ash/system_web_apps/apps/DEPS b/chrome/browser/ash/system_web_apps/apps/DEPS
index b37f0f8..67d60af 100644
--- a/chrome/browser/ash/system_web_apps/apps/DEPS
+++ b/chrome/browser/ash/system_web_apps/apps/DEPS
@@ -1,3 +1,76 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/system_web_apps/apps",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/accessibility/media_app",
+  "+chrome/browser/apps/almanac_api_client",
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/upload_office_to_cloud",
+  "+chrome/browser/devtools",
+  "+chrome/browser/error_reporting",
+  "+chrome/browser/extensions/component_loader.h",
+  "+chrome/browser/extensions/extension_tab_util.h",
+  "+chrome/browser/lifetime",
+  "+chrome/browser/media/webrtc",
+  "+chrome/browser/metrics",
+  "+chrome/browser/notifications",
+  "+chrome/browser/platform_util.h",
+  "+chrome/browser/policy",
+  "+chrome/browser/profiles",
+  "+chrome/browser/scalable_iph",
+  "+chrome/browser/ui/ash",
+  "+chrome/browser/ui/browser_commands.h",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_list_observer.h",
+  "+chrome/browser/ui/browser_navigator.h",
+  "+chrome/browser/ui/browser_navigator_params.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/chrome_pages.h",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/browser/ui/webui/ash",
+  "+chrome/browser/ui/webui/chrome_web_ui_controller_factory.h",
+  "+chrome/browser/ui/webui/plural_string_handler.h",
+  "+chrome/browser/ui/webui/sanitized_image_source.h",
+  "+chrome/browser/web_applications/extension_status_utils.h",
+  "+chrome/browser/web_applications/mojom",
+  "+chrome/browser/web_applications/web_app_command_scheduler.h",
+  "+chrome/browser/web_applications/web_app_constants.h",
+  "+chrome/browser/web_applications/web_app_helpers.h",
+  "+chrome/browser/web_applications/web_app_id_constants.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_launch_queue.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_tab_helper.h",
+  "+chrome/browser/web_applications/web_app_utils.h",
+  "+chrome/common/channel_info.h",
+  "+chrome/common/chrome_constants.h",
+  "+chrome/common/chrome_paths.h",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/common/pref_names.h",
+  "+chrome/common/url_constants.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
+
 specific_include_rules = {
   "eche_app_integration_browsertest.cc": [
     "+chrome/browser/ui/views",
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils.cc
index 5b1f3c6..26722a75 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils.cc
@@ -7,10 +7,12 @@
 #include <memory>
 #include <string_view>
 
+#include "ash/constants/ash_features.h"
 #include "ash/webui/personalization_app/personalization_app_ui.h"
 #include "base/base64.h"
 #include "base/logging.h"
 #include "base/strings/strcat.h"
+#include "chrome/browser/ash/login/demo_mode/demo_session.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_ambient_provider_impl.h"
 #include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_keyboard_backlight_provider_impl.h"
@@ -106,6 +108,13 @@
     return true;
   }
 
+  if (features::IsSeaPenDemoModeEnabled() &&
+      DemoSession::IsDeviceInDemoMode()) {
+    DVLOG(1) << __func__ << " demo mode";
+    const auto* user = GetUser(profile);
+    return user && user->GetType() == user_manager::UserType::kPublicAccount;
+  }
+
   // Do not show for managed profiles.
   if (profile->GetProfilePolicyConnector()->IsManaged()) {
     DVLOG(1) << __func__ << " managed profile";
@@ -123,6 +132,10 @@
     case user_manager::UserType::kArcKioskApp:
     case user_manager::UserType::kWebKioskApp:
     case user_manager::UserType::kChild:
+    // Demo mode retail devices are type kPublicAccount and may have been
+    // handled earlier in this function. But not all kPublicAccount users are in
+    // demo mode. Public ChromeOS devices at libraries etc often use
+    // kPublicAccount type.
     case user_manager::UserType::kPublicAccount:
     case user_manager::UserType::kGuest:
       return false;
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils_unittest.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils_unittest.cc
index 4e693f5..71087d1 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils_unittest.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils_unittest.cc
@@ -4,13 +4,18 @@
 
 #include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils.h"
 
+#include "ash/constants/ash_features.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/login/demo_mode/demo_session.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
 #include "components/account_id/account_id.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "components/user_manager/user_manager.h"
+#include "components/user_manager/user_type.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -18,29 +23,29 @@
 
 namespace {
 
-void AddAndLoginRegularUser(const AccountId& account_id) {
+void AddAndLoginUser(const AccountId& account_id,
+                     const user_manager::UserType user_type) {
   ash::FakeChromeUserManager* user_manager =
       static_cast<ash::FakeChromeUserManager*>(
           user_manager::UserManager::Get());
-  auto* user = user_manager->AddUser(account_id);
-  user_manager->LoginUser(user->GetAccountId());
-  user_manager->SwitchActiveUser(user->GetAccountId());
-}
-
-void AddAndLoginChildUser(const AccountId& account_id) {
-  ash::FakeChromeUserManager* user_manager =
-      static_cast<ash::FakeChromeUserManager*>(
-          user_manager::UserManager::Get());
-  auto* user = user_manager->AddChildUser(account_id);
-  user_manager->LoginUser(user->GetAccountId());
-  user_manager->SwitchActiveUser(user->GetAccountId());
-}
-
-void AddAndLoginGuestUser() {
-  ash::FakeChromeUserManager* user_manager =
-      static_cast<ash::FakeChromeUserManager*>(
-          user_manager::UserManager::Get());
-  auto* user = user_manager->AddGuestUser();
+  user_manager::User* user = nullptr;
+  switch (user_type) {
+    case user_manager::UserType::kRegular:
+      user = user_manager->AddUser(account_id);
+      break;
+    case user_manager::UserType::kGuest:
+      ASSERT_TRUE(account_id.empty());
+      user = user_manager->AddGuestUser();
+      break;
+    case user_manager::UserType::kChild:
+      user = user_manager->AddChildUser(account_id);
+      break;
+    case user_manager::UserType::kPublicAccount:
+      user = user_manager->AddPublicAccountUser(account_id);
+      break;
+    default:
+      GTEST_FAIL() << "Unsupported user type " << user_type;
+  }
   user_manager->LoginUser(user->GetAccountId());
   user_manager->SwitchActiveUser(user->GetAccountId());
 }
@@ -49,7 +54,13 @@
  public:
   PersonalizationAppUtilsTest()
       : scoped_user_manager_(std::make_unique<ash::FakeChromeUserManager>()),
-        profile_manager_(TestingBrowserProcess::GetGlobal()) {}
+        profile_manager_(TestingBrowserProcess::GetGlobal()) {
+    scoped_feature_list_.InitWithFeatures(
+        {features::kSeaPen, features::kSeaPenDemoMode,
+         features::kFeatureManagementSeaPen},
+        {});
+  }
+
   PersonalizationAppUtilsTest(const PersonalizationAppUtilsTest&) = delete;
   PersonalizationAppUtilsTest& operator=(const PersonalizationAppUtilsTest&) =
       delete;
@@ -73,6 +84,7 @@
   }
 
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
   content::BrowserTaskEnvironment task_environment_;
   user_manager::ScopedUserManager scoped_user_manager_;
   TestingProfileManager profile_manager_;
@@ -80,15 +92,16 @@
 
 TEST_F(PersonalizationAppUtilsTest, IsEligibleForSeaPen_Guest) {
   auto* guest_profile = profile_manager().CreateGuestProfile();
-  AddAndLoginGuestUser();
+  AddAndLoginUser(EmptyAccountId(), user_manager::UserType::kGuest);
   ASSERT_FALSE(IsEligibleForSeaPen(guest_profile));
 }
 
 TEST_F(PersonalizationAppUtilsTest, IsEligibleForSeaPen_Child) {
-  auto* child_profile =
-      profile_manager().CreateTestingProfile("child@example.com");
+  const std::string email = "child@example.com";
+  auto* child_profile = profile_manager().CreateTestingProfile(email);
   child_profile->SetIsSupervisedProfile();
-  AddAndLoginChildUser(AccountId::FromUserEmail("child@example.com"));
+  AddAndLoginUser(AccountId::FromUserEmail(email),
+                  user_manager::UserType::kChild);
   ASSERT_FALSE(IsEligibleForSeaPen(child_profile));
 }
 
@@ -97,30 +110,62 @@
 }
 
 TEST_F(PersonalizationAppUtilsTest, IsEligibleForSeaPen_Googler) {
-  auto* googler_profile =
-      profile_manager().CreateTestingProfile("user@google.com");
+  const std::string email = "user@google.com";
+  auto* googler_profile = profile_manager().CreateTestingProfile(email);
   googler_profile->GetProfilePolicyConnector()->OverrideIsManagedForTesting(
       true);
-  AddAndLoginRegularUser(AccountId::FromUserEmail("user@google.com"));
+  AddAndLoginUser(AccountId::FromUserEmail(email),
+                  user_manager::UserType::kRegular);
   ASSERT_TRUE(IsEligibleForSeaPen(googler_profile));
 }
 
 TEST_F(PersonalizationAppUtilsTest, IsEligibleForSeaPen_Managed) {
-  auto* managed_profile =
-      profile_manager().CreateTestingProfile("managed@example.com");
+  const std::string email = "managed@example.com";
+  auto* managed_profile = profile_manager().CreateTestingProfile(email);
   managed_profile->GetProfilePolicyConnector()->OverrideIsManagedForTesting(
       true);
-  AddAndLoginRegularUser(AccountId::FromUserEmail("managed@example.com"));
+  AddAndLoginUser(AccountId::FromUserEmail(email),
+                  user_manager::UserType::kRegular);
   ASSERT_FALSE(IsEligibleForSeaPen(managed_profile));
 }
 
 TEST_F(PersonalizationAppUtilsTest, IsEligibleForSeaPen_Regular) {
-  auto* regular_profile =
-      profile_manager().CreateTestingProfile("user@example.com");
-  AddAndLoginRegularUser(AccountId::FromUserEmail("user@example.com"));
+  const std::string email = "user@example.com";
+  auto* regular_profile = profile_manager().CreateTestingProfile(email);
+  AddAndLoginUser(AccountId::FromUserEmail(email),
+                  user_manager::UserType::kRegular);
   ASSERT_TRUE(IsEligibleForSeaPen(regular_profile));
 }
 
+TEST_F(PersonalizationAppUtilsTest, IsEligibleForSeaPen_PublicAccount) {
+  const std::string email = "public-account@example.com";
+  auto* managed_profile = profile_manager().CreateTestingProfile(email);
+  managed_profile->GetProfilePolicyConnector()->OverrideIsManagedForTesting(
+      true);
+  AddAndLoginUser(AccountId::FromUserEmail(email),
+                  user_manager::UserType::kPublicAccount);
+
+  ASSERT_FALSE(IsEligibleForSeaPen(managed_profile));
+}
+
+TEST_F(PersonalizationAppUtilsTest, IsEligibleForSeaPen_PublicAccountDemoMode) {
+  const std::string email = "demo-public-account@example.com";
+  auto* managed_profile = profile_manager().CreateTestingProfile(email);
+  managed_profile->GetProfilePolicyConnector()->OverrideIsManagedForTesting(
+      true);
+  AddAndLoginUser(AccountId::FromUserEmail(email),
+                  user_manager::UserType::kPublicAccount);
+
+  // Force demo mode on.
+  ASSERT_FALSE(::ash::DemoSession::IsDeviceInDemoMode());
+  static_cast<StubInstallAttributes*>(StubInstallAttributes::Get())
+      ->SetDemoMode();
+  ASSERT_TRUE(::ash::DemoSession::IsDeviceInDemoMode());
+
+  ASSERT_TRUE(IsEligibleForSeaPen(managed_profile))
+      << "Demo mode should force enable SeaPen for managed profile";
+}
+
 }  // namespace
 
 }  // namespace ash::personalization_app
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/wallpaper_metrics_provider.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/wallpaper_metrics_provider.cc
index 8f0d3fb..e9be626 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/wallpaper_metrics_provider.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/wallpaper_metrics_provider.cc
@@ -5,11 +5,24 @@
 #include "chrome/browser/ash/system_web_apps/apps/personalization_app/wallpaper_metrics_provider.h"
 
 #include "ash/public/cpp/wallpaper/wallpaper_types.h"
+#include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
+#include "ash/wallpaper/sea_pen_wallpaper_manager.h"
 #include "ash/wallpaper/wallpaper_controller_impl.h"
 #include "base/check.h"
 #include "base/metrics/histogram_functions.h"
 
+namespace {
+
+void OnTemplateIdFromFileExtracted(std::optional<int> template_id) {
+  if (!template_id.has_value()) {
+    return;
+  }
+  base::UmaHistogramSparse("Ash.Wallpaper.SeaPen.Template.Settled",
+                           *template_id);
+}
+
+}  // namespace
 WallpaperMetricsProvider::WallpaperMetricsProvider() = default;
 WallpaperMetricsProvider::~WallpaperMetricsProvider() = default;
 
@@ -22,20 +35,38 @@
 
   auto* wallpaper_controller = ash::Shell::Get()->wallpaper_controller();
   auto info = wallpaper_controller->GetActiveUserWallpaperInfo();
-  if (!info || !ash::IsOnlineWallpaper(info->type)) {
+  if (!info) {
     return;
   }
-  base::UmaHistogramBoolean("Ash.Wallpaper.Image.Settled.HasUnitId",
-                            info->unit_id.has_value());
-  if (info->unit_id.has_value()) {
-    base::UmaHistogramSparse("Ash.Wallpaper.Image.Settled",
-                             info->unit_id.value());
+
+  if (ash::IsOnlineWallpaper(info->type)) {
+    base::UmaHistogramBoolean("Ash.Wallpaper.Image.Settled.HasUnitId",
+                              info->unit_id.has_value());
+    if (info->unit_id.has_value()) {
+      base::UmaHistogramSparse("Ash.Wallpaper.Image.Settled",
+                               info->unit_id.value());
+    }
+    base::UmaHistogramBoolean("Ash.Wallpaper.Image.Settled.HasCollectionId",
+                              !info->collection_id.empty());
+    if (!info->collection_id.empty()) {
+      const int collection_id_hash = base::PersistentHash(info->collection_id);
+      base::UmaHistogramSparse("Ash.Wallpaper.Collection.Settled",
+                               collection_id_hash);
+    }
+    return;
   }
-  base::UmaHistogramBoolean("Ash.Wallpaper.Image.Settled.HasCollectionId",
-                            !info->collection_id.empty());
-  if (!info->collection_id.empty()) {
-    const int collection_id_hash = base::PersistentHash(info->collection_id);
-    base::UmaHistogramSparse("Ash.Wallpaper.Collection.Settled",
-                             collection_id_hash);
+
+  if (info->type == ash::WallpaperType::kSeaPen) {
+    uint32_t sea_pen_id;
+    if (!base::StringToUint(info->location, &sea_pen_id)) {
+      LOG(ERROR) << __func__ << "invalid key for Sea Pen wallpaper";
+      return;
+    }
+
+    const AccountId account_id =
+        ash::Shell::Get()->session_controller()->GetActiveAccountId();
+
+    ash::SeaPenWallpaperManager::GetInstance()->GetTemplateIdFromFile(
+        account_id, sea_pen_id, base::BindOnce(&OnTemplateIdFromFileExtracted));
   }
 }
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/wallpaper_metrics_provider_unittest.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/wallpaper_metrics_provider_unittest.cc
index 4b0b412..43e48c5c 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/wallpaper_metrics_provider_unittest.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/wallpaper_metrics_provider_unittest.cc
@@ -6,24 +6,56 @@
 
 #include <optional>
 
+#include "ash/public/cpp/test/in_process_data_decoder.h"
 #include "ash/public/cpp/wallpaper/wallpaper_types.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/test/ash_test_util.h"
+#include "ash/wallpaper/test_sea_pen_wallpaper_manager_session_delegate.h"
 #include "ash/wallpaper/wallpaper_controller_impl.h"
+#include "ash/webui/common/mojom/sea_pen.mojom.h"
 #include "base/hash/hash.h"
+#include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/test_future.h"
 #include "components/account_id/account_id.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/codec/jpeg_codec.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_unittest_util.h"
 
 namespace {
 
+constexpr char kUser[] = "user1@test.com";
+const AccountId kAccountId = AccountId::FromUserEmailGaiaId(kUser, kUser);
+
+ash::personalization_app::mojom::SeaPenQueryPtr MakeTemplateQuery() {
+  return ash::personalization_app::mojom::SeaPenQuery::NewTemplateQuery(
+      ash::personalization_app::mojom::SeaPenTemplateQuery::New(
+          ash::personalization_app::mojom::SeaPenTemplateId::kFlower,
+          ::base::flat_map<
+              ash::personalization_app::mojom::SeaPenTemplateChip,
+              ash::personalization_app::mojom::SeaPenTemplateOption>(),
+          ash::personalization_app::mojom::SeaPenUserVisibleQuery::New(
+              "test template query", "test template title")));
+}
+
 class WallpaperMetricsProviderTest : public ash::AshTestBase {
  public:
   WallpaperMetricsProvider& wallpaper_metrics_provider() {
     return wallpaper_metrics_provider_;
   }
 
+  void SetUp() override {
+    ash::AshTestBase::SetUp();
+    ash::SeaPenWallpaperManager::GetInstance()->SetSessionDelegateForTesting(
+        std::make_unique<ash::TestSeaPenWallpaperManagerSessionDelegate>());
+  }
+
  private:
+  ash::InProcessDataDecoder decoder_;
   WallpaperMetricsProvider wallpaper_metrics_provider_;
 };
 
@@ -99,4 +131,39 @@
   histogram_tester.ExpectTotalCount("Ash.Wallpaper.Collection.Settled", 0);
 }
 
+TEST_F(WallpaperMetricsProviderTest, RecordsSeaPenTemplateSettled) {
+  SimulateUserLogin(kAccountId);
+  AccountId account_id =
+      ash::Shell::Get()->session_controller()->GetActiveAccountId();
+
+  gfx::ImageSkia* image = nullptr;
+  std::string jpg_bytes = ash::CreateEncodedImageForTesting(
+      {1, 1}, SK_ColorBLUE, data_decoder::mojom::ImageCodec::kDefault, image);
+  ASSERT_TRUE(!jpg_bytes.empty());
+
+  const uint32_t image_id = 111;
+  base::test::TestFuture<bool> save_image_future;
+  ash::SeaPenWallpaperManager::GetInstance()->SaveSeaPenImage(
+      account_id, {std::move(jpg_bytes), image_id}, MakeTemplateQuery(),
+      save_image_future.GetCallback());
+  ASSERT_TRUE(save_image_future.Get());
+
+  auto* wallpaper_controller = ash::Shell::Get()->wallpaper_controller();
+  ash::WallpaperInfo info(base::NumberToString(image_id),
+                          ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
+                          ash::WallpaperType::kSeaPen, base::Time::Now());
+  wallpaper_controller->SetUserWallpaperInfo(account_id, info);
+
+  base::HistogramTester histogram_tester;
+
+  wallpaper_metrics_provider().ProvideCurrentSessionData(nullptr);
+
+  base::RunLoop().RunUntilIdle();
+  histogram_tester.ExpectUniqueSample(
+      "Ash.Wallpaper.SeaPen.Template.Settled",
+      static_cast<int>(
+          ash::personalization_app::mojom::SeaPenTemplateId::kFlower),
+      1);
+}
+
 }  // namespace
diff --git a/chrome/browser/ash/system_web_apps/test_support/DEPS b/chrome/browser/ash/system_web_apps/test_support/DEPS
new file mode 100644
index 0000000..2128f756
--- /dev/null
+++ b/chrome/browser/ash/system_web_apps/test_support/DEPS
@@ -0,0 +1,41 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/system_web_apps/test_support",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/system_web_apps",
+  "+chrome/browser/ui/browser_finder.h",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/browser_list.h",
+  "+chrome/browser/ui/browser_window.h",
+  "+chrome/browser/ui/tabs",
+  "+chrome/browser/ui/web_applications",
+  "+chrome/browser/ui/webui/webui_util.h",
+  "+chrome/browser/web_applications/mojom",
+  "+chrome/browser/web_applications/os_integration",
+  "+chrome/browser/web_applications/test",
+  "+chrome/browser/web_applications/web_app_helpers.h",
+  "+chrome/browser/web_applications/web_app_icon_generator.h",
+  "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_provider.h",
+  "+chrome/browser/web_applications/web_app_registrar.h",
+  "+chrome/browser/web_applications/web_app_utils.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+  "+chrome/test/interaction",
+]
diff --git a/chrome/browser/ash/system_web_apps/types/DEPS b/chrome/browser/ash/system_web_apps/types/DEPS
new file mode 100644
index 0000000..090c6c0d
--- /dev/null
+++ b/chrome/browser/ash/system_web_apps/types/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/system_web_apps/types",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/telemetry_extension/DEPS b/chrome/browser/ash/telemetry_extension/DEPS
new file mode 100644
index 0000000..68420da
--- /dev/null
+++ b/chrome/browser/ash/telemetry_extension/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/telemetry_extension",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/telemetry_extension/common/DEPS b/chrome/browser/ash/telemetry_extension/common/DEPS
new file mode 100644
index 0000000..0054b2c
--- /dev/null
+++ b/chrome/browser/ash/telemetry_extension/common/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/telemetry_extension/common",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/telemetry_extension/diagnostics/DEPS b/chrome/browser/ash/telemetry_extension/diagnostics/DEPS
new file mode 100644
index 0000000..e6eb8ed
--- /dev/null
+++ b/chrome/browser/ash/telemetry_extension/diagnostics/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/telemetry_extension/diagnostics",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/telemetry_extension/events/DEPS b/chrome/browser/ash/telemetry_extension/events/DEPS
new file mode 100644
index 0000000..dab7ec64
--- /dev/null
+++ b/chrome/browser/ash/telemetry_extension/events/DEPS
@@ -0,0 +1,19 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/telemetry_extension/events",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/telemetry_extension/common",
+  "+chrome/browser/ash/telemetry_extension/telemetry",
+]
diff --git a/chrome/browser/ash/telemetry_extension/management/DEPS b/chrome/browser/ash/telemetry_extension/management/DEPS
new file mode 100644
index 0000000..1e95c2b
--- /dev/null
+++ b/chrome/browser/ash/telemetry_extension/management/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/telemetry_extension/management",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/telemetry_extension/routines/DEPS b/chrome/browser/ash/telemetry_extension/routines/DEPS
new file mode 100644
index 0000000..9ff9d46
--- /dev/null
+++ b/chrome/browser/ash/telemetry_extension/routines/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/telemetry_extension/routines",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/telemetry_extension/common",
+]
diff --git a/chrome/browser/ash/telemetry_extension/telemetry/DEPS b/chrome/browser/ash/telemetry_extension/telemetry/DEPS
new file mode 100644
index 0000000..be11914
--- /dev/null
+++ b/chrome/browser/ash/telemetry_extension/telemetry/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/telemetry_extension/telemetry",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+]
diff --git a/chrome/browser/ash/tether/DEPS b/chrome/browser/ash/tether/DEPS
new file mode 100644
index 0000000..4d9b3c1
--- /dev/null
+++ b/chrome/browser/ash/tether/DEPS
@@ -0,0 +1,26 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/tether",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/device_sync",
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/multidevice_setup",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/secure_channel",
+  "+chrome/browser/prefs",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/network",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/trusted_vault/DEPS b/chrome/browser/ash/trusted_vault/DEPS
new file mode 100644
index 0000000..cab19bd
--- /dev/null
+++ b/chrome/browser/ash/trusted_vault/DEPS
@@ -0,0 +1,20 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/trusted_vault",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/browser/trusted_vault",
+]
diff --git a/chrome/browser/ash/usb/DEPS b/chrome/browser/ash/usb/DEPS
new file mode 100644
index 0000000..5f8218f
--- /dev/null
+++ b/chrome/browser/ash/usb/DEPS
@@ -0,0 +1,28 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/usb",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/arc",
+  "+chrome/browser/ash/bruschetta",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/guest_os",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/notifications",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/settings_window_manager_chromeos.h",
+  "+chrome/common/webui_url_constants.h",
+  "+chrome/grit",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/video_conference/DEPS b/chrome/browser/ash/video_conference/DEPS
new file mode 100644
index 0000000..10f95bb
--- /dev/null
+++ b/chrome/browser/ash/video_conference/DEPS
@@ -0,0 +1,34 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/video_conference",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/apps/app_service",
+  "+chrome/browser/ash/borealis",
+  "+chrome/browser/ash/camera_mic",
+  "+chrome/browser/ash/crosapi",
+  "+chrome/browser/ash/crostini",
+  "+chrome/browser/ash/plugin_vm",
+  "+chrome/browser/ash/system_web_apps",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/chromeos/video_conference",
+  "+chrome/browser/content_settings",
+  "+chrome/browser/media/webrtc",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/ash/system_web_apps",
+  "+chrome/browser/ui/browser.h",
+  "+chrome/browser/ui/views/frame",
+  "+chrome/common/chrome_switches.h",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/ash/wallpaper/DEPS b/chrome/browser/ash/wallpaper/DEPS
new file mode 100644
index 0000000..54137570
--- /dev/null
+++ b/chrome/browser/ash/wallpaper/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/wallpaper",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/drive",
+  "+chrome/browser/ash/file_manager",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/profiles",
+  "+chrome/browser/ui/browser.h",
+]
diff --git a/chrome/browser/ash/wallpaper_handlers/DEPS b/chrome/browser/ash/wallpaper_handlers/DEPS
new file mode 100644
index 0000000..8decdd2
--- /dev/null
+++ b/chrome/browser/ash/wallpaper_handlers/DEPS
@@ -0,0 +1,26 @@
+include_rules = [
+  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
+  # details.
+  "-chrome",
+
+  # This directory is in //chrome, which violates the rule above. Allow this
+  # directory to #include its own files.
+  "+chrome/browser/ash/wallpaper_handlers",
+
+  # Existing dependencies within //chrome. There is an active effort to
+  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
+  # Whenever possible, avoid adding new //chrome dependencies to this list.
+  #
+  # Files residing in certain directories (e.g., //chrome/browser) are listed
+  # individually. Other dependencies within //chrome are listed on a per-
+  # directory basis. See //tools/chromeos/gen_deps.sh for details.
+  "+chrome/browser/ash/login/users",
+  "+chrome/browser/ash/profiles",
+  "+chrome/browser/ash/settings",
+  "+chrome/browser/browser_process.h",
+  "+chrome/browser/manta",
+  "+chrome/browser/net",
+  "+chrome/browser/profiles",
+  "+chrome/browser/signin",
+  "+chrome/test/base",
+]
diff --git a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
index 8e1d0f21..47c09eb 100644
--- a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
+++ b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
@@ -557,7 +557,6 @@
             }
 
             public Iban build() {
-                assert mValue != null && !mValue.isEmpty() : "IBAN value can't be null or empty.";
                 switch (mRecordType) {
                     case IbanRecordType.UNKNOWN:
                         assert mGuid.isEmpty()
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index b11a816..c0f9870 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -5499,6 +5499,10 @@
   return std::make_unique<ChromeTracingDelegate>();
 }
 
+bool ChromeContentBrowserClient::IsSystemWideTracingEnabled() {
+  return ChromeTracingDelegate::IsSystemWideTracingEnabled();
+}
+
 bool ChromeContentBrowserClient::IsPluginAllowedToCallRequestOSFileHandle(
     content::BrowserContext* browser_context,
     const GURL& url) {
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 11c05e56..c4a678a 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -495,6 +495,7 @@
   std::optional<base::TimeDelta> GetSpareRendererDelayForSiteURL(
       const GURL& site_url) override;
   std::unique_ptr<content::TracingDelegate> CreateTracingDelegate() override;
+  bool IsSystemWideTracingEnabled() override;
   bool IsPluginAllowedToCallRequestOSFileHandle(
       content::BrowserContext* browser_context,
       const GURL& url) override;
diff --git a/chrome/browser/chromeos/echo/DEPS b/chrome/browser/chromeos/echo/DEPS
new file mode 100644
index 0000000..43be987d
--- /dev/null
+++ b/chrome/browser/chromeos/echo/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  # Guarded by IS_CHROMEOS_ASH.
+  "+chrome/browser/ash/crosapi",
+]
diff --git a/chrome/browser/chromeos/echo/echo_util.cc b/chrome/browser/chromeos/echo/echo_util.cc
new file mode 100644
index 0000000..ab17955
--- /dev/null
+++ b/chrome/browser/chromeos/echo/echo_util.cc
@@ -0,0 +1,61 @@
+// 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/chromeos/echo/echo_util.h"
+
+#include "base/strings/string_util.h"
+#include "base/task/bind_post_task.h"
+#include "build/chromeos_buildflags.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "base/check_is_test.h"
+#include "chrome/browser/ash/crosapi/crosapi_ash.h"
+#include "chrome/browser/ash/crosapi/crosapi_manager.h"
+#include "chrome/browser/ash/crosapi/echo_private_ash.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/crosapi/mojom/echo_private.mojom.h"
+#include "chromeos/lacros/lacros_service.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+namespace chromeos::echo_util {
+namespace {
+
+// Performs an explicit conversion from `str` to `base::ok(str)`.
+base::ok<std::string> ToExpected(const std::string& str) {
+  return base::ok(str);
+}
+
+}  // namespace
+
+void GetOobeTimestamp(GetOobeTimestampCallback callback) {
+  // NOTE: Ensure that `callback` will run asynchronously.
+  callback = base::BindPostTaskToCurrentDefault(std::move(callback));
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  if (crosapi::CrosapiManager::IsInitialized()) {
+    crosapi::CrosapiManager::Get()
+        ->crosapi_ash()
+        ->echo_private_ash()
+        ->GetOobeTimestamp(
+            base::BindOnce(&ToExpected).Then(std::move(callback)));
+  } else {
+    CHECK_IS_TEST();
+    std::move(callback).Run(base::ok(std::string()));
+  }
+#else  // BUILDFLAG(IS_CHROMEOS_ASH)
+  auto* lacros_service = chromeos::LacrosService::Get();
+  if (lacros_service->IsAvailable<crosapi::mojom::EchoPrivate>() &&
+      static_cast<uint32_t>(
+          lacros_service->GetInterfaceVersion<crosapi::mojom::EchoPrivate>()) >=
+          crosapi::mojom::EchoPrivate::kGetOobeTimestampMinVersion) {
+    lacros_service->GetRemote<crosapi::mojom::EchoPrivate>()->GetOobeTimestamp(
+        base::BindOnce(&ToExpected).Then(std::move(callback)));
+  } else {
+    std::move(callback).Run(base::unexpected("EchoPrivate unavailable."));
+  }
+#endif
+}
+
+}  // namespace chromeos::echo_util
diff --git a/chrome/browser/chromeos/echo/echo_util.h b/chrome/browser/chromeos/echo/echo_util.h
new file mode 100644
index 0000000..8185a73
--- /dev/null
+++ b/chrome/browser/chromeos/echo/echo_util.h
@@ -0,0 +1,31 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_ECHO_ECHO_UTIL_H_
+#define CHROME_BROWSER_CHROMEOS_ECHO_ECHO_UTIL_H_
+
+#include <string>
+
+#include "base/functional/callback_forward.h"
+#include "base/types/expected.h"
+
+static_assert(BUILDFLAG(IS_CHROMEOS));
+
+// TODO(http://b/333583704): Revert CL which added this util after migration.
+namespace chromeos::echo_util {
+
+using GetOobeTimestampCallback = base::OnceCallback<void(
+    base::expected</*value=*/std::string, /*error=*/std::string>
+        oobe_timestamp_or_error)>;
+
+// Asynchronously returns the OOBE timestamp corresponding to the time of device
+// registration. For backwards compatibility:
+// * The OOBE timestamp is a "y-M-d" formatted GMT date string.
+// * An empty string is returned if OOBE timestamp is unavailable.
+// * An error is returned in Lacros if the required CrosAPI is unavailable.
+void GetOobeTimestamp(GetOobeTimestampCallback callback);
+
+}  // namespace chromeos::echo_util
+
+#endif  // CHROME_BROWSER_CHROMEOS_ECHO_ECHO_UTIL_H_
diff --git a/chrome/browser/chromeos/extensions/echo_private/echo_private_api.cc b/chrome/browser/chromeos/extensions/echo_private/echo_private_api.cc
index 4df4671d..7a174dd 100644
--- a/chrome/browser/chromeos/extensions/echo_private/echo_private_api.cc
+++ b/chrome/browser/chromeos/extensions/echo_private/echo_private_api.cc
@@ -15,6 +15,7 @@
 #include "base/values.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/echo/echo_util.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -181,31 +182,18 @@
 }
 
 ExtensionFunction::ResponseAction EchoPrivateGetOobeTimestampFunction::Run() {
-  auto callback = base::BindOnce(
-      &EchoPrivateGetOobeTimestampFunction::RespondWithResult, this);
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  crosapi::CrosapiManager::Get()
-      ->crosapi_ash()
-      ->echo_private_ash()
-      ->GetOobeTimestamp(std::move(callback));
-#else
-  auto* lacros_service = chromeos::LacrosService::Get();
-  if (lacros_service->IsAvailable<crosapi::mojom::EchoPrivate>() &&
-      static_cast<uint32_t>(
-          lacros_service->GetInterfaceVersion<crosapi::mojom::EchoPrivate>()) >=
-          crosapi::mojom::EchoPrivate::kGetOobeTimestampMinVersion) {
-    lacros_service->GetRemote<crosapi::mojom::EchoPrivate>()->GetOobeTimestamp(
-        std::move(callback));
-  } else {
-    return RespondNow(Error("EchoPrivate unavailable."));
-  }
-#endif
+  chromeos::echo_util::GetOobeTimestamp(base::BindOnce(
+      &EchoPrivateGetOobeTimestampFunction::RespondWithResult, this));
   return RespondLater();
 }
 
 void EchoPrivateGetOobeTimestampFunction::RespondWithResult(
-    const std::string& timestamp) {
-  Respond(WithArguments(timestamp));
+    base::expected<std::string, std::string> timestamp_or_error) {
+  if (timestamp_or_error.has_value()) {
+    Respond(WithArguments(std::move(timestamp_or_error.value())));
+    return;
+  }
+  Respond(Error(std::move(timestamp_or_error.error())));
 }
 
 EchoPrivateGetUserConsentFunction::EchoPrivateGetUserConsentFunction() =
diff --git a/chrome/browser/chromeos/extensions/echo_private/echo_private_api.h b/chrome/browser/chromeos/extensions/echo_private/echo_private_api.h
index 2bf6cd4..7d80d57 100644
--- a/chrome/browser/chromeos/extensions/echo_private/echo_private_api.h
+++ b/chrome/browser/chromeos/extensions/echo_private/echo_private_api.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/types/expected.h"
 #include "base/values.h"
 #include "extensions/browser/extension_function.h"
 
@@ -49,7 +50,8 @@
   ResponseAction Run() override;
 
  private:
-  void RespondWithResult(const std::string& timestamp);
+  void RespondWithResult(
+      base::expected<std::string, std::string> timestamp_or_error);
 
   DECLARE_EXTENSION_FUNCTION("echoPrivate.getOobeTimestamp",
                              ECHOPRIVATE_GETOOBETIMESTAMP)
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
index d5f296f..1cc15ef 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
+++ b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
@@ -90,12 +90,23 @@
   return crosapi::mojom::FSPChangeType::kChanged;
 }
 
+crosapi::mojom::CloudFileInfoPtr ParseCloudFileInfo(
+    const std::optional<api::file_system_provider::CloudFileInfo>&
+        cloud_file_info) {
+  if (!cloud_file_info.has_value()) {
+    return nullptr;
+  }
+  return crosapi::mojom::CloudFileInfo::New(
+      cloud_file_info.value().version_tag);
+}
+
 // Convert the change from the IDL type to mojom type.
 crosapi::mojom::FSPChangePtr ParseChange(
     const api::file_system_provider::Change& change) {
   crosapi::mojom::FSPChangePtr result = crosapi::mojom::FSPChange::New();
   result->path = base::FilePath::FromUTF8Unsafe(change.entry_path);
   result->type = ParseChangeType(change.change_type);
+  result->cloud_file_info = ParseCloudFileInfo(change.cloud_file_info);
   return result;
 }
 
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/routines/diagnostic_routine_converters.cc b/chrome/browser/chromeos/extensions/telemetry/api/routines/diagnostic_routine_converters.cc
index 9a4e971..58c9103e 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/routines/diagnostic_routine_converters.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/routines/diagnostic_routine_converters.cc
@@ -99,7 +99,11 @@
   cx_diag::MemoryRoutineFinishedDetail detail =
       UncheckedConvertPtr(std::move(input));
   result.bytes_tested = std::move(detail.bytes_tested);
-  result.result = std::move(detail.result);
+  if (detail.result) {
+    result.result = cx_diag::LegacyMemtesterResult();
+    result.result->passed_items = std::move(detail.result->passed_items);
+    result.result->failed_items = std::move(detail.result->failed_items);
+  }
   return result;
 }
 
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/routines/diagnostic_routine_observation_browsertest.cc b/chrome/browser/chromeos/extensions/telemetry/api/routines/diagnostic_routine_observation_browsertest.cc
index fc59c86..96574b0f 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/routines/diagnostic_routine_observation_browsertest.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/routines/diagnostic_routine_observation_browsertest.cc
@@ -475,16 +475,16 @@
       async function canObserveOnRoutineFinishedWithMemoryDetail() {
         chrome.os.diagnostics.onRoutineFinished.addListener((event) => {
           chrome.test.assertEq(event, {
-            "has_passed": true,
+            "hasPassed": true,
             "detail": {
               "memory": {
                 "bytesTested": 500,
                 "result": {
-                  "failed_items": [
+                  "failedItems": [
                     "compare_and",
                     "compare_sub"
                   ],
-                  "passed_items": [
+                  "passedItems": [
                     "compare_div",
                     "compare_mul"
                   ]
@@ -538,7 +538,7 @@
         chrome.os.diagnostics.onRoutineFinished.addListener(
           (event) => {
             chrome.test.assertEq(event, {
-              "has_passed": true,
+              "hasPassed": true,
               "uuid":"%s"
             });
 
@@ -588,12 +588,12 @@
           chrome.test.assertEq(event, {
             "detail": {
               "fan": {
-                "passed_fan_ids": [0],
-                "failed_fan_ids": [1],
-                "fan_count_status": "matched"
+                "passedFanIds": [0],
+                "failedFanIds": [1],
+                "fanCountStatus": "matched"
               }
             },
-            "has_passed": true,
+            "hasPassed": true,
             "uuid":"%s"
           });
 
diff --git a/chrome/browser/chromeos/mahi/mahi_web_contents_manager.cc b/chrome/browser/chromeos/mahi/mahi_web_contents_manager.cc
index a3718fc..3cb43e7 100644
--- a/chrome/browser/chromeos/mahi/mahi_web_contents_manager.cc
+++ b/chrome/browser/chromeos/mahi/mahi_web_contents_manager.cc
@@ -8,6 +8,7 @@
 #include <optional>
 #include <string>
 
+#include "ash/constants/ash_pref_names.h"
 #include "base/containers/contains.h"
 #include "base/containers/fixed_flat_set.h"
 #include "base/functional/bind.h"
@@ -19,7 +20,9 @@
 #include "chrome/browser/chromeos/mahi/mahi_browser_util.h"
 #include "chrome/browser/chromeos/mahi/mahi_content_extraction_delegate.h"
 #include "chrome/browser/favicon/favicon_utils.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chromeos/crosapi/mojom/mahi.mojom-forward.h"
+#include "components/prefs/pref_service.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
@@ -124,6 +127,20 @@
   return focused_web_content_state_.is_distillable.value();
 }
 
+bool MahiWebContentsManager::GetPrefValue() const {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  Profile* profile = ProfileManager::GetActiveUserProfile();
+  if (!profile || !profile->GetPrefs()) {
+    return false;
+  }
+  return profile->GetPrefs()->GetBoolean(ash::prefs::kMahiEnabled);
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  return mahi_pref_lacros_;
+#endif
+}
+
 // static
 void MahiWebContentsManager::SetInstanceForTesting(
     MahiWebContentsManager* test_manager) {
diff --git a/chrome/browser/chromeos/mahi/mahi_web_contents_manager.h b/chrome/browser/chromeos/mahi/mahi_web_contents_manager.h
index e14f356..6a82ce12 100644
--- a/chrome/browser/chromeos/mahi/mahi_web_contents_manager.h
+++ b/chrome/browser/chromeos/mahi/mahi_web_contents_manager.h
@@ -67,6 +67,10 @@
   // distillability has not been checked yet.
   bool IsFocusedPageDistillable();
 
+  // Virtual so that can be overridden in test.
+  virtual bool GetPrefValue() const;
+  void set_mahi_pref_lacros(bool value) { mahi_pref_lacros_ = value; }
+
  private:
   friend base::NoDestructor<MahiWebContentsManager>;
   // Friends to access some test-only functions.
@@ -108,6 +112,8 @@
   std::unique_ptr<MahiBrowserClientImpl> client_;
   bool is_initialized_ = false;
 
+  bool mahi_pref_lacros_ = false;
+
   // The state of the web content which get focus in the browser.
   WebContentState focused_web_content_state_{/*url=*/GURL(), /*title=*/u""};
   // The state of the web content which is requested by the user.
diff --git a/chrome/browser/chromeos/mahi/test/fake_mahi_web_contents_manager.cc b/chrome/browser/chromeos/mahi/test/fake_mahi_web_contents_manager.cc
index efe11d6d..601acb4 100644
--- a/chrome/browser/chromeos/mahi/test/fake_mahi_web_contents_manager.cc
+++ b/chrome/browser/chromeos/mahi/test/fake_mahi_web_contents_manager.cc
@@ -28,6 +28,10 @@
   client_->GetContent(focused_web_content_state().page_id, std::move(callback));
 }
 
+bool FakeMahiWebContentsManager::GetPrefValue() const {
+  return pref_state_;
+}
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void FakeMahiWebContentsManager::SetMahiBrowserDelegateForTesting(
     crosapi::mojom::MahiBrowserDelegate* delegate) {
diff --git a/chrome/browser/chromeos/mahi/test/fake_mahi_web_contents_manager.h b/chrome/browser/chromeos/mahi/test/fake_mahi_web_contents_manager.h
index 157e9870..0157fd01 100644
--- a/chrome/browser/chromeos/mahi/test/fake_mahi_web_contents_manager.h
+++ b/chrome/browser/chromeos/mahi/test/fake_mahi_web_contents_manager.h
@@ -45,6 +45,9 @@
   void RequestContentFromPage(const base::UnguessableToken& page_id,
                               GetContentCallback callback);
 
+  bool GetPrefValue() const override;
+  void SetPrefForTesting(bool pref_state) { pref_state_ = pref_state; }
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   void SetMahiBrowserDelegateForTesting(
       crosapi::mojom::MahiBrowserDelegate* delegate);
@@ -52,6 +55,8 @@
   void BindMahiBrowserDelegateForTesting(
       mojo::PendingRemote<crosapi::mojom::MahiBrowserDelegate> pending_remote);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+ private:
+  bool pref_state_ = true;
 };
 
 }  // namespace mahi
diff --git a/chrome/browser/compose/compose_session.cc b/chrome/browser/compose/compose_session.cc
index 40cd78a..078f466 100644
--- a/chrome/browser/compose/compose_session.cc
+++ b/chrome/browser/compose/compose_session.cc
@@ -602,7 +602,6 @@
   SaveMostRecentOkStateToUndoStack();
   current_state_->response->undo_available = !undo_states_.empty();
   most_recent_ok_state_->SetMojoState(current_state_->Clone());
-  most_recent_ok_state_->UnsetUserEdited();
 
   ui_response->undo_available = !undo_states_.empty();
   if (dialog_remote_.is_bound()) {
@@ -853,45 +852,8 @@
 }
 
 void ComposeSession::EditResult(const std::string& new_result) {
-  // Nothing has changed. Ignore.
-  if (new_result == most_recent_ok_state_->mojo_state()->response->result) {
-    return;
-  }
-
-  // If user undid edits, then pop from stack and return.
-  if (most_recent_ok_state_->is_user_edited() &&
-      most_recent_ok_state_->original_response() == new_result) {
-    current_state_->response->result = new_result;
-    most_recent_ok_state_->UnsetUserEdited();
-    most_recent_ok_state_->SetModelingLogEntry(
-        undo_states_.top()->TakeModelingLogEntry());
-    most_recent_ok_state_->SetMojoState(undo_states_.top()->TakeMojoState());
-    current_state_->response->undo_available =
-        most_recent_ok_state_->mojo_state()->response->undo_available;
-    undo_states_.pop();
-    return;
-  }
-
-  // Save the state if it hasn't been edited.
-  if (!most_recent_ok_state_->is_user_edited()) {
-    SaveMostRecentOkStateToUndoStack();
-
-    // Restore the states that were moved away as part of the undo save with
-    // copies.
-    most_recent_ok_state_->SetMojoState(current_state_.Clone());
-
-    // Set the user edited field in the restored ok state.
-    most_recent_ok_state_->SetUserEdited(
-        most_recent_ok_state_->mojo_state()->response->result);
-
-    // Update the undo_available field to show that there is an undo state.
-    most_recent_ok_state_->mojo_state()->response->undo_available = true;
-    current_state_->response->undo_available = true;
-  }
-
-  // Update result to be the edited result.
-  most_recent_ok_state_->mojo_state()->response->result = new_result;
-  current_state_->response->result = new_result;
+  // TODO(b/333084626): empty stub, re-implemented with dependent editable
+  // results changes.
 }
 
 void ComposeSession::InitializeWithText(const std::optional<std::string>& text,
diff --git a/chrome/browser/data_sharing/BUILD.gn b/chrome/browser/data_sharing/BUILD.gn
index fba43509..64f635e 100644
--- a/chrome/browser/data_sharing/BUILD.gn
+++ b/chrome/browser/data_sharing/BUILD.gn
@@ -16,6 +16,7 @@
     deps = [
       "//base:base_java",
       "//build/android:build_java",
+      "//chrome/browser/data_sharing/android:ui_delegate_java",
       "//chrome/browser/profiles/android:java",
       "//components/data_sharing/public:public_java",
       "//third_party/androidx:androidx_annotation_annotation_java",
diff --git a/chrome/browser/data_sharing/android/BUILD.gn b/chrome/browser/data_sharing/android/BUILD.gn
index 874f7cb2..b2efe60 100644
--- a/chrome/browser/data_sharing/android/BUILD.gn
+++ b/chrome/browser/data_sharing/android/BUILD.gn
@@ -10,6 +10,7 @@
 
   deps = [
     "//base:base_java",
+    "//chrome/browser/profiles/android:java",
     "//chrome/browser/tab:java",
     "//third_party/androidx:androidx_annotation_annotation_java",
   ]
@@ -28,6 +29,7 @@
 
   deps = [
     "//base:base_java",
+    "//chrome/browser/profiles/android:java",
     "//chrome/browser/tab:java",
     "//third_party/androidx:androidx_annotation_annotation_java",
   ]
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactory.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactory.java
index 2c06ec5a..fd29727 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactory.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactory.java
@@ -9,6 +9,7 @@
 import org.jni_zero.NativeMethods;
 
 import org.chromium.base.ResettersForTesting;
+import org.chromium.base.UserDataHost;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.data_sharing.DataSharingService;
 
@@ -45,6 +46,21 @@
         ResettersForTesting.register(() -> sDataSharingServiceForTesting = null);
     }
 
+    /**
+     * A factory method to create or retrieve a {@link DataSharingUIDelegate} object for a given
+     * profile.
+     *
+     * @return The {@link DataSharingUIDelegate} for the given profile.
+     */
+    public static DataSharingUIDelegate getUIDelegate(Profile profile) {
+        UserDataHost host = getForProfile(profile).getUserDataHost();
+        DataSharingUIDelegate uiDelegate = host.getUserData(DataSharingUIDelegateImpl.class);
+        return uiDelegate != null
+                ? uiDelegate
+                : host.setUserData(
+                        DataSharingUIDelegateImpl.class, new DataSharingUIDelegateImpl(profile));
+    }
+
     @NativeMethods
     interface Natives {
         DataSharingService getForProfile(Profile profile);
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java
index 62b92ed..e3f13f0 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java
@@ -13,6 +13,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.UserDataHost;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
@@ -52,6 +53,11 @@
                     public DataSharingNetworkLoader getNetworkLoader() {
                         return null;
                     }
+
+                    @Override
+                    public UserDataHost getUserDataHost() {
+                        return null;
+                    }
                 };
 
         DataSharingServiceFactory.setForTesting(testService);
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingUIDelegateImpl.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingUIDelegateImpl.java
index 74b5be9..ba0f6cc 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingUIDelegateImpl.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingUIDelegateImpl.java
@@ -11,19 +11,21 @@
 import androidx.annotation.NonNull;
 
 import org.chromium.base.Callback;
+import org.chromium.base.UserData;
 import org.chromium.chrome.browser.data_sharing.configs.AvatarConfig;
 import org.chromium.chrome.browser.data_sharing.configs.GroupMemberConfig;
 import org.chromium.chrome.browser.data_sharing.configs.MemberPickerConfig;
+import org.chromium.chrome.browser.profiles.Profile;
 
 import java.util.List;
 
 /** Implementation of {@link DataSharingUIDelegate}. */
-public class DataSharingUIDelegateImpl implements DataSharingUIDelegate {
+class DataSharingUIDelegateImpl implements DataSharingUIDelegate, UserData {
 
-    private final String mProfileId;
+    private final Profile mProfile;
 
-    public DataSharingUIDelegateImpl(String profileId) {
-        mProfileId = profileId;
+    DataSharingUIDelegateImpl(Profile profile) {
+        mProfile = profile;
     }
 
     @Override
diff --git a/chrome/browser/extensions/api/tab_groups/tab_groups_api_unittest.cc b/chrome/browser/extensions/api/tab_groups/tab_groups_api_unittest.cc
index 41b6c81..9bed1f6 100644
--- a/chrome/browser/extensions/api/tab_groups/tab_groups_api_unittest.cc
+++ b/chrome/browser/extensions/api/tab_groups/tab_groups_api_unittest.cc
@@ -376,8 +376,6 @@
 
 // Test that tabGroups.update() passes on a saved group.
 TEST_F(TabGroupsApiUnitTest, TabGroupsUpdateSavedTab) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   TabStripModel* tab_strip_model = browser()->tab_strip_model();
   ASSERT_TRUE(tab_strip_model->SupportsTabGroups());
 
diff --git a/chrome/browser/extensions/api/tab_groups/tab_groups_util.cc b/chrome/browser/extensions/api/tab_groups/tab_groups_util.cc
index 52eab90..a41b546 100644
--- a/chrome/browser/extensions/api/tab_groups/tab_groups_util.cc
+++ b/chrome/browser/extensions/api/tab_groups/tab_groups_util.cc
@@ -184,11 +184,6 @@
 
 bool IsGroupSaved(tab_groups::TabGroupId tab_group_id,
                   TabStripModel* tab_strip_model) {
-  // If the feature is turned off, then the tab is not in a saved group.
-  if (!base::FeatureList::IsEnabled(features::kTabGroupsSave)) {
-    return false;
-  }
-
   // If the service failed to start, then there are no saved tab groups.
   tab_groups::SavedTabGroupKeyedService* saved_tab_group_service =
       tab_groups::SavedTabGroupServiceFactory::GetForProfile(
diff --git a/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc b/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc
index 8951c08..6586594 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc
@@ -511,8 +511,6 @@
 
 // Tests that calling chrome.tabs.update does not update a saved tab.
 TEST_F(TabsApiUnitTest, TabsUpdateSavedTabGroupTab) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   const GURL kExampleCom("http://example.com");
   const GURL kChromiumOrg("https://chromium.org");
 
@@ -700,8 +698,6 @@
 // only.
 TEST_F(TabsApiUnitTest,
        TabsUpdateSavedTabGroupTabAllowedForLockedFullscreenPermission) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   scoped_refptr<const Extension> extension =
       ExtensionBuilder("UpdateTest")
           .SetID("pmgljoohajacndjcjlajcopidgnhphcl")
@@ -923,8 +919,6 @@
 
 // Tests that calling chrome.tabs.move doesn't move a saved tab.
 TEST_F(TabsApiUnitTest, TabsMoveSavedTabGroupTabNotAllowed) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   scoped_refptr<const Extension> extension =
       ExtensionBuilder("MoveWithinWindowTest").Build();
 
@@ -991,8 +985,6 @@
 // locked fullscreen permission. Locked fullscreen permission is ChromeOS only.
 TEST_F(TabsApiUnitTest,
        TabsMoveSavedTabGroupTabAllowedForLockedFullscreenPermission) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   scoped_refptr<const Extension> extension =
       ExtensionBuilder("MoveWithinWindowTest")
           .SetID("pmgljoohajacndjcjlajcopidgnhphcl")
@@ -1293,8 +1285,6 @@
 
 // Test that grouping tabs that are in a saved group should fail.
 TEST_F(TabsApiUnitTest, TabsGroupForSavedTabGroupTabNotAllowed) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
 
   scoped_refptr<const Extension> extension =
@@ -1371,8 +1361,6 @@
 // is ChromeOS only.
 TEST_F(TabsApiUnitTest,
        TabsGroupForSavedTabGroupTabAllowedForLockedFullscreenPermission) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
 
   scoped_refptr<const Extension> extension =
@@ -1490,8 +1478,6 @@
 
 // Test that the tabs.ungroup does not ungroup a SavedTabGroup.
 TEST_F(TabsApiUnitTest, TabsUngroupSingleGroupForSavedTabGroupNotAllowed) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
 
   scoped_refptr<const Extension> extension =
@@ -1560,8 +1546,6 @@
 TEST_F(
     TabsApiUnitTest,
     TabsUngroupSingleGroupForSavedTabGroupAllowedForLockedFullscreenPermission) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
 
   scoped_refptr<const Extension> extension =
@@ -1750,8 +1734,6 @@
 }
 
 TEST_F(TabsApiUnitTest, TabsGoForwardAndBackSavedTabGroupTabNotAllowed) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   scoped_refptr<const Extension> extension_with_tabs_permission =
       CreateTabsExtension();
 
@@ -1830,8 +1812,6 @@
 TEST_F(
     TabsApiUnitTest,
     TabsGoForwardAndBackSavedTabGroupTabAllowedForLockedFullscreenPermission) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   scoped_refptr<const Extension> extension =
       ExtensionBuilder("GoForwardAndBackTest")
           .SetID("pmgljoohajacndjcjlajcopidgnhphcl")
@@ -2165,8 +2145,6 @@
 
 // Tests that calling chrome.tabs.discard on a saved tab does not discard.
 TEST_F(TabsApiUnitTest, TabsDiscardSavedTabGroupTabNotAllowed) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   scoped_refptr<const Extension> extension =
       ExtensionBuilder("DiscardTest").Build();
   const GURL kExampleCom("http://example.com");
@@ -2227,8 +2205,6 @@
 // is ChromeOS only.
 TEST_F(TabsApiUnitTest,
        TabsDiscardSavedTabGroupTabAllowedForLockedFullscreenPermission) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kTabGroupsSave}, {});
   scoped_refptr<const Extension> extension =
       ExtensionBuilder("DiscardTest")
           .SetID("pmgljoohajacndjcjlajcopidgnhphcl")
diff --git a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
index c44f0daf..f221501 100644
--- a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
+++ b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
@@ -188,21 +188,6 @@
              ::switches::kOndeviceHandwritingSwitch) == "use_rootfs";
 }
 
-bool IsHandwritingLegacyRecognitionEnabled() {
-  // Disable handwriting DLC flags if device does not have on-device handwriting
-  // (see b/316981973).
-  return IsOndeviceHandwritingEnabledViaCommandLine() &&
-         base::FeatureList::IsEnabled(
-             ash::features::kHandwritingLegacyRecognition);
-}
-
-bool IsHandwritingLibraryDlcEnabled() {
-  // Disable handwriting DLC flags if device does not have on-device handwriting
-  // (see b/316981973).
-  return IsOndeviceHandwritingEnabledViaCommandLine() &&
-         base::FeatureList::IsEnabled(ash::features::kHandwritingLibraryDlc);
-}
-
 }  // namespace
 
 namespace extensions {
@@ -524,8 +509,6 @@
   features.Append(GenerateFeatureFlag("autocorrect", config.auto_correct));
   features.Append(GenerateFeatureFlag("spellcheck", config.spell_check));
   features.Append(GenerateFeatureFlag("handwriting", config.handwriting));
-  features.Append(GenerateFeatureFlag("handwritinglegacyrecognition",
-                                      IsHandwritingLegacyRecognitionEnabled()));
   features.Append(GenerateFeatureFlag(
       "hindiinscriptlayout",
       base::FeatureList::IsEnabled(ash::features::kHindiInscriptLayout)));
@@ -548,8 +531,6 @@
   features.Append(GenerateFeatureFlag(
       "autocorrectparamstuning",
       base::FeatureList::IsEnabled(ash::features::kAutocorrectParamsTuning)));
-  features.Append(GenerateFeatureFlag("handwritinglibrarydlc",
-                                      IsHandwritingLibraryDlcEnabled()));
   features.Append(
       GenerateFeatureFlag("jelly", chromeos::features::IsJellyEnabled()));
   features.Append(GenerateFeatureFlag(
diff --git a/chrome/browser/extensions/extension_context_menu_model.cc b/chrome/browser/extensions/extension_context_menu_model.cc
index 950a6cf0..b6aaf3b1 100644
--- a/chrome/browser/extensions/extension_context_menu_model.cc
+++ b/chrome/browser/extensions/extension_context_menu_model.cc
@@ -155,7 +155,7 @@
     case ExtensionContextMenuModel::PAGE_ACCESS_SUBMENU:
     case ExtensionContextMenuModel::PAGE_ACCESS_ALL_EXTENSIONS_GRANTED:
     case ExtensionContextMenuModel::PAGE_ACCESS_ALL_EXTENSIONS_BLOCKED:
-      NOTREACHED();
+      DUMP_WILL_BE_NOTREACHED_NORETURN();
       break;
     case ExtensionContextMenuModel::VIEW_WEB_PERMISSIONS:
       return ContextMenuAction::kViewWebPermissions;
diff --git a/chrome/browser/extensions/extension_keybinding_apitest.cc b/chrome/browser/extensions/extension_keybinding_apitest.cc
index 1701f56..b4091d1e 100644
--- a/chrome/browser/extensions/extension_keybinding_apitest.cc
+++ b/chrome/browser/extensions/extension_keybinding_apitest.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/extensions/extension_action_test_helper.h"
+#include "chrome/browser/ui/extensions/extensions_container.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
 #include "chrome/test/base/interactive_test_utils.h"
@@ -379,7 +380,8 @@
   std::unique_ptr<ExtensionActionTestHelper> test_helper =
       ExtensionActionTestHelper::Create(browser());
   RunScheduledLayouts();
-  EXPECT_EQ(0, test_helper->VisibleBrowserActions());
+  EXPECT_FALSE(test_helper->GetExtensionsContainer()->IsActionVisibleOnToolbar(
+      extension->id()));
 
   const int tab_id = NavigateToTestURLAndReturnTabId();
   SetActionVisibleOnTab(profile(), *extension, tab_id);
diff --git a/chrome/browser/extensions/extension_tab_util.cc b/chrome/browser/extensions/extension_tab_util.cc
index ec50f23..fe05e3a 100644
--- a/chrome/browser/extensions/extension_tab_util.cc
+++ b/chrome/browser/extensions/extension_tab_util.cc
@@ -1148,11 +1148,6 @@
 // static
 bool ExtensionTabUtil::TabIsInSavedTabGroup(content::WebContents* contents,
                                             TabStripModel* tab_strip_model) {
-  // If the feature is turned off, then the tab is not in a saved group.
-  if (!base::FeatureList::IsEnabled(features::kTabGroupsSave)) {
-    return false;
-  }
-
   // If the tab_strip_model is empty, find the contents in one of the browsers.
   if (!tab_strip_model) {
     CHECK(contents);
diff --git a/chrome/browser/extensions/user_script_world_configuration_manager_unittest.cc b/chrome/browser/extensions/user_script_world_configuration_manager_unittest.cc
new file mode 100644
index 0000000..9c74e35
--- /dev/null
+++ b/chrome/browser/extensions/user_script_world_configuration_manager_unittest.cc
@@ -0,0 +1,61 @@
+// 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 "extensions/browser/user_script_world_configuration_manager.h"
+
+#include "chrome/browser/extensions/extension_service_test_with_install.h"
+#include "extensions/test/test_extension_dir.h"
+
+namespace extensions {
+
+using UserScriptWorldConfigurationManagerTest = ExtensionServiceTestWithInstall;
+
+// Tests that extension-specified world configurations are cleared on
+// extension update. This matches the behavior of the registered content and
+// user scripts.
+TEST_F(UserScriptWorldConfigurationManagerTest,
+       ConfigurationsAreClearedOnExtensionUpdate) {
+  InitializeEmptyExtensionService();
+
+  UserScriptWorldConfigurationManager* manager =
+      UserScriptWorldConfigurationManager::Get(browser_context());
+
+  static constexpr char kManifest[] =
+      R"({
+           "name": "World Configuration",
+           "version": "%s",
+           "manifest_version": 3,
+           "permissions": ["userScripts"]
+         })";
+  auto get_manifest = [](const char* version) {
+    return base::StringPrintf(kManifest, version);
+  };
+  TestExtensionDir extension_dir;
+
+  extension_dir.WriteManifest(get_manifest("0.1"));
+  base::FilePath crx_v1 = extension_dir.Pack("v1.crx");
+
+  extension_dir.WriteManifest(get_manifest("0.2"));
+  base::FilePath crx_v2 = extension_dir.Pack("v2.crx");
+
+  const Extension* extension = InstallCRX(crx_v1, INSTALL_NEW);
+  ASSERT_TRUE(extension);
+
+  // Register two different configurations for user script worlds, one for the
+  // default world and another for "world 1".
+  manager->SetUserScriptWorldInfo(
+      *extension, std::nullopt, "script-src: self", /*enable_messaging=*/false);
+  manager->SetUserScriptWorldInfo(
+      *extension, "world 1", "script-src: none", /*enable_messaging=*/false);
+  EXPECT_EQ(2u, manager->GetAllUserScriptWorlds(extension->id()).size());
+
+  extension = InstallCRX(crx_v2, INSTALL_UPDATED);
+  ASSERT_TRUE(extension);
+
+  // Since the extension updated to a new version, the world configurations
+  // should have been removed.
+  EXPECT_EQ(0u, manager->GetAllUserScriptWorlds(extension->id()).size());
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 7fa61d5e..3418dd0 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1838,7 +1838,7 @@
   {
     "name": "defer-tab-switcher-layout-creation",
     "owners": [ "ckitagawa@google.com", "clank-isochron-team@google.com" ],
-    "expiry_milestone": 125
+    "expiry_milestone": 127
   },
   {
     "name": "demo-mode-test-tag",
@@ -2482,6 +2482,11 @@
     "expiry_milestone": 130
   },
   {
+    "name": "enable-compression-dictionary-transport-allow-http2",
+    "owners": [ "horo@chromium.org", "net-dev@chromium.org" ],
+    "expiry_milestone": 130
+  },
+  {
     "name": "enable-compression-dictionary-transport-backend",
     "owners": [ "horo@chromium.org", "net-dev@chromium.org" ],
     "expiry_milestone": 130
@@ -4684,6 +4689,11 @@
     "expiry_milestone": 130
   },
   {
+    "name": "gate-nv12-gmb-video-frames-on-hw-support",
+    "owners": [ "blundell@chromium.org", "chrome-gpu-team@google.com" ],
+    "expiry_milestone": 130
+  },
+  {
     "name": "gesture-properties-dbus-service",
     "owners": [ "hcutts@chromium.org", "chromeos-tango@google.com" ],
     // Used by developers for debugging and input device tuning.
@@ -4739,26 +4749,11 @@
     "expiry_milestone": 116
   },
   {
-    "name": "grid-tab-switcher-android-animations",
-    "owners": [ "ckitagawa@google.com", "clank-isochron-team@google.com" ],
-    "expiry_milestone": 125
-  },
-  {
     "name": "growth-campaigns",
     "owners": [ "llin@google.com", "cros-growth@google.com" ],
     "expiry_milestone": 130
   },
   {
-    "name": "handwriting-legacy-recognition",
-    "owners": [ "curtismcmullan@chromium.org", "essential-inputs-team@google.com" ],
-    "expiry_milestone": 118
-  },
-  {
-    "name": "handwriting-library-dlc",
-    "owners": [ "shend@google.com", "essential-inputs-team@google.com" ],
-    "expiry_milestone": 118
-  },
-  {
     "name": "happiness-tracking-surveys-for-desktop-demo",
     "owners": [ "//chrome/browser/ui/hats/OWNERS" ],
     // A debugging and demo flag to allow UI/dev/testing team to always show the UI
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index dbfd798..bb33b76 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -901,6 +901,12 @@
     "When this is enabled, Chromium can use stored shared dictionaries even "
     "when the connection is using HTTP/1 for non-localhost requests.";
 
+const char kCompressionDictionaryTransportOverHttp2Name[] =
+    "Compression dictionary transport over HTTP/2";
+const char kCompressionDictionaryTransportOverHttp2Description[] =
+    "When this is enabled, Chromium can use stored shared dictionaries even "
+    "when the connection is using HTTP/2 for non-localhost requests.";
+
 const char kCompressionDictionaryTransportRequireKnownRootCertName[] =
     "Compression dictionary transport require knwon root cert";
 const char kCompressionDictionaryTransportRequireKnownRootCertDescription[] =
@@ -1960,17 +1966,6 @@
 const char kContextualPageActionsShareModelDescription[] =
     "Enables share model data collection.";
 
-const char kHandwritingLegacyRecognitionName[] =
-    "Handwriting Legacy Recognition";
-const char kHandwritingLegacyRecognitionDescription[] =
-    "Enables new on-device recognition for handwriting legacy paths.";
-
-const char kHandwritingLibraryDlcName[] =
-    "Handwriting recognition with library from DLC";
-const char kHandwritingLibraryDlcDescription[] =
-    "Enables new on-device recognition with the handwriting library installed "
-    "from DLC";
-
 const char kHardwareMediaKeyHandling[] = "Hardware Media Key Handling";
 const char kHardwareMediaKeyHandlingDescription[] =
     "Enables using media keys to control the active media session. This "
@@ -3276,18 +3271,6 @@
 const char kCommerceDeveloperDescription[] =
     "Allows users in the allowlist to enter the developer mode";
 
-const char kTabGroupsSaveId[] = "tab-groups-save";
-const char kTabGroupsSaveName[] = "Tab Groups Save and Sync";
-const char kTabGroupsSaveDescription[] =
-    "Enable saving and recalling of tab groups. Right click a tab group to "
-    "save it. Recall groups from the bookmarks bar.";
-
-const char kTabGroupsSaveV2Id[] = "tab-groups-save-v2";
-const char kTabGroupsSaveV2Name[] = "Tab Groups Save and Sync V2";
-const char kTabGroupsSaveV2Description[] =
-    "Enables saving and recalling of tab groups but enhanced. Highly "
-    "experimental.";
-
 const char kTabHoverCardImagesName[] = "Tab Hover Card Images";
 const char kTabHoverCardImagesDescription[] =
     "Shows a preview image in tab hover cards, if tab hover cards are enabled.";
@@ -4306,12 +4289,6 @@
     "Secondary option in `Site settings` to request the desktop version of "
     "websites based on window width.";
 
-const char kGridTabSwitcherAndroidAnimationsName[] =
-    "Grid tab switcher Android animations";
-const char kGridTabSwitcherAndroidAnimationsDescription[] =
-    "Run grid tab switcher shrink & expand animations in Android instead of "
-    "using the compositor.";
-
 const char kRevokeNotificationsPermissionIfDisabledOnAppLevelName[] =
     "Revoke site-level notification permission on Android";
 const char kRevokeNotificationsPermissionIfDisabledOnAppLevelDescription[] =
@@ -7574,6 +7551,12 @@
     "handlers won't be registered, making it possible to install another "
     "version for testing.";
 
+const char kGateNV12GMBVideoFramesOnHWSupportName[] =
+    "Gate NV12 GpuMemoryBuffer VideoFrames on hardware support.";
+const char kGateNV12GMBVideoFramesOnHWSupportDescription[] =
+    "Gates enabling client-side use of GPUMemoryBuffers for NV12 video frames "
+    "on hardware support for NV12 format being present.";
+
 const char kLacrosColorManagementName[] = "Enable Chrome Color Management.";
 const char kLacrosColorManagementDescription[] =
     "Uses chrome-color-management wayland protocol to manage color spaces "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 24f6e8aa..abe2fad 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -534,6 +534,9 @@
 extern const char kCompressionDictionaryTransportOverHttp1Name[];
 extern const char kCompressionDictionaryTransportOverHttp1Description[];
 
+extern const char kCompressionDictionaryTransportOverHttp2Name[];
+extern const char kCompressionDictionaryTransportOverHttp2Description[];
+
 extern const char kCompressionDictionaryTransportRequireKnownRootCertName[];
 extern const char
     kCompressionDictionaryTransportRequireKnownRootCertDescription[];
@@ -1124,12 +1127,6 @@
 extern const char kGpuRasterizationName[];
 extern const char kGpuRasterizationDescription[];
 
-extern const char kHandwritingLegacyRecognitionName[];
-extern const char kHandwritingLegacyRecognitionDescription[];
-
-extern const char kHandwritingLibraryDlcName[];
-extern const char kHandwritingLibraryDlcDescription[];
-
 extern const char kHardwareMediaKeyHandling[];
 extern const char kHardwareMediaKeyHandlingDescription[];
 
@@ -1926,14 +1923,6 @@
 extern const char kCommerceDeveloperName[];
 extern const char kCommerceDeveloperDescription[];
 
-extern const char kTabGroupsSaveId[];
-extern const char kTabGroupsSaveName[];
-extern const char kTabGroupsSaveDescription[];
-
-extern const char kTabGroupsSaveV2Id[];
-extern const char kTabGroupsSaveV2Name[];
-extern const char kTabGroupsSaveV2Description[];
-
 extern const char kTabHoverCardImagesName[];
 extern const char kTabHoverCardImagesDescription[];
 
@@ -2042,7 +2031,6 @@
 extern const char kVerticalAutomotiveBackButtonToolbarName[];
 extern const char kVerticalAutomotiveBackButtonToolbarDescription[];
 
-
 extern const char kVcBackgroundReplaceName[];
 extern const char kVcBackgroundReplaceDescription[];
 
@@ -2435,9 +2423,6 @@
 extern const char kRefreshFeedOnRestartName[];
 extern const char kRefreshFeedOnRestartDescription[];
 
-extern const char kGridTabSwitcherAndroidAnimationsName[];
-extern const char kGridTabSwitcherAndroidAnimationsDescription[];
-
 extern const char kInfoCardAcknowledgementTrackingName[];
 extern const char kInfoCardAcknowledgementTrackingDescription[];
 
@@ -4384,6 +4369,9 @@
 extern const char kDisableOfficeEditingComponentAppName[];
 extern const char kDisableOfficeEditingComponentAppDescription[];
 
+extern const char kGateNV12GMBVideoFramesOnHWSupportName[];
+extern const char kGateNV12GMBVideoFramesOnHWSupportDescription[];
+
 extern const char kLacrosColorManagementName[];
 extern const char kLacrosColorManagementDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index fcdc6ce..b17eadc 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -671,6 +671,8 @@
              "FullscreenInsetsApiMigrationOnAutomotive",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// TODO(b/330367117): This flag should be cleaned up when phase 1 of AndroidHub
+// launches as launching AndroidHub simplifies its removal.
 BASE_FEATURE(kGridTabSwitcherAndroidAnimations,
              "GridTabSwitcherAndroidAnimations",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/browser/hang_monitor/hang_crash_dump_win.cc b/chrome/browser/hang_monitor/hang_crash_dump_win.cc
index b9a49c2..529a72f 100644
--- a/chrome/browser/hang_monitor/hang_crash_dump_win.cc
+++ b/chrome/browser/hang_monitor/hang_crash_dump_win.cc
@@ -20,8 +20,7 @@
 
 void CrashDumpHungChildProcess(base::ProcessHandle handle) {
   HANDLE remote_thread = InjectDumpForHungInput_ExportThunk(handle);
-  DCHECK(remote_thread) << "Failed creating remote thread: error "
-                        << GetLastError();
+  DPCHECK(remote_thread) << "Failed creating remote thread";
   if (remote_thread) {
     WaitForSingleObject(remote_thread, kGenerateDumpTimeoutMS);
     CloseHandle(remote_thread);
diff --git a/chrome/browser/history_embeddings/history_embeddings_service_browsertest.cc b/chrome/browser/history_embeddings/history_embeddings_service_browsertest.cc
index aba99b6..84a3396 100644
--- a/chrome/browser/history_embeddings/history_embeddings_service_browsertest.cc
+++ b/chrome/browser/history_embeddings/history_embeddings_service_browsertest.cc
@@ -129,6 +129,12 @@
 
   histogram_tester.ExpectUniqueSample(
       "History.Embeddings.QueryEmbeddingSucceeded", true, 1);
+  histogram_tester.ExpectUniqueSample(
+      "History.Embeddings.VisibilityModelAvailableAtQuery", true, 1);
+  histogram_tester.ExpectUniqueSample("History.Embeddings.NumUrlsMatched", 1,
+                                      1);
+  histogram_tester.ExpectUniqueSample(
+      "History.Embeddings.NumMatchedUrlsVisible", 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_F(
@@ -144,11 +150,50 @@
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
   EXPECT_TRUE(store_future.Wait());
 
+  base::HistogramTester histogram_tester;
+
   // Search for the passage.
   base::test::TestFuture<SearchResult> search_future;
   service()->Search("A B C D e f g", 1, search_future.GetCallback());
   SearchResult result = search_future.Take();
   EXPECT_TRUE(result.empty());
+
+  histogram_tester.ExpectUniqueSample(
+      "History.Embeddings.QueryEmbeddingSucceeded", true, 1);
+  histogram_tester.ExpectUniqueSample(
+      "History.Embeddings.VisibilityModelAvailableAtQuery", true, 1);
+  histogram_tester.ExpectUniqueSample("History.Embeddings.NumUrlsMatched", 1,
+                                      1);
+  histogram_tester.ExpectUniqueSample(
+      "History.Embeddings.NumMatchedUrlsVisible", 0, 1);
+}
+
+IN_PROC_BROWSER_TEST_F(HistoryEmbeddingsBrowserTest,
+                       SearchReturnsNoResultsVisibilityModelNotAvailable) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  base::test::TestFuture<UrlPassages> store_future;
+  callback_for_tests() = store_future.GetRepeatingCallback();
+
+  const GURL url = embedded_test_server()->GetURL("/inner_text/test1.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+  EXPECT_TRUE(store_future.Wait());
+
+  base::HistogramTester histogram_tester;
+
+  // Search for the passage.
+  base::test::TestFuture<SearchResult> search_future;
+  service()->Search("A B C D e f g", 1, search_future.GetCallback());
+  SearchResult result = search_future.Take();
+  EXPECT_TRUE(result.empty());
+
+  histogram_tester.ExpectUniqueSample(
+      "History.Embeddings.QueryEmbeddingSucceeded", true, 1);
+  histogram_tester.ExpectUniqueSample(
+      "History.Embeddings.VisibilityModelAvailableAtQuery", false, 1);
+  histogram_tester.ExpectUniqueSample("History.Embeddings.NumUrlsMatched", 1,
+                                      1);
+  histogram_tester.ExpectUniqueSample(
+      "History.Embeddings.NumMatchedUrlsVisible", 0, 1);
 }
 
 }  // namespace history_embeddings
diff --git a/chrome/browser/lacros/embedded_a11y_manager_lacros.cc b/chrome/browser/lacros/embedded_a11y_manager_lacros.cc
index 919c98c9..9db0a946 100644
--- a/chrome/browser/lacros/embedded_a11y_manager_lacros.cc
+++ b/chrome/browser/lacros/embedded_a11y_manager_lacros.cc
@@ -260,10 +260,22 @@
   }
 }
 
+void EmbeddedA11yManagerLacros::SetReadingModeEnabled(bool enabled) {
+  if (reading_mode_enabled_ != enabled) {
+    reading_mode_enabled_ = enabled;
+    UpdateEmbeddedA11yHelperExtension();
+  }
+}
+
+bool EmbeddedA11yManagerLacros::IsReadingModeEnabled() {
+  return reading_mode_enabled_;
+}
+
 void EmbeddedA11yManagerLacros::UpdateEmbeddedA11yHelperExtension() {
   // Switch Access and Select to Speak share a helper extension which has a
   // manifest content script to tell Google Docs to annotate the HTML canvas.
-  if (select_to_speak_enabled_ || switch_access_enabled_) {
+  if (select_to_speak_enabled_ || switch_access_enabled_ ||
+      reading_mode_enabled_) {
     EmbeddedA11yExtensionLoader::GetInstance()->InstallExtensionWithId(
         extension_misc::kEmbeddedA11yHelperExtensionId,
         extension_misc::kEmbeddedA11yHelperExtensionPath,
diff --git a/chrome/browser/lacros/embedded_a11y_manager_lacros.h b/chrome/browser/lacros/embedded_a11y_manager_lacros.h
index 62a32f8..4a6b8593 100644
--- a/chrome/browser/lacros/embedded_a11y_manager_lacros.h
+++ b/chrome/browser/lacros/embedded_a11y_manager_lacros.h
@@ -63,6 +63,10 @@
   void AddFocusChangedCallbackForTest(
       base::RepeatingCallback<void(gfx::Rect)> callback);
 
+  void SetReadingModeEnabled(bool enabled);
+
+  bool IsReadingModeEnabled();
+
  private:
   EmbeddedA11yManagerLacros();
   ~EmbeddedA11yManagerLacros() override;
@@ -104,6 +108,7 @@
   bool chromevox_enabled_ = false;
   bool select_to_speak_enabled_ = false;
   bool switch_access_enabled_ = false;
+  bool reading_mode_enabled_ = false;
   std::optional<bool> pdf_ocr_always_active_enabled_;
 
   base::RepeatingClosure speak_selected_text_callback_for_test_;
diff --git a/chrome/browser/lacros/embedded_a11y_manager_lacros_browsertest.cc b/chrome/browser/lacros/embedded_a11y_manager_lacros_browsertest.cc
index 5179aea..16e78ef 100644
--- a/chrome/browser/lacros/embedded_a11y_manager_lacros_browsertest.cc
+++ b/chrome/browser/lacros/embedded_a11y_manager_lacros_browsertest.cc
@@ -310,6 +310,23 @@
 }
 
 IN_PROC_BROWSER_TEST_F(EmbeddedA11yManagerLacrosTest,
+                       AddsAndRemovesHelperForReadingMode) {
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  const auto& profiles = profile_manager->GetLoadedProfiles();
+  ASSERT_GT(profiles.size(), 0u);
+  Profile* profile = profiles[0];
+
+  auto* embedded_a11y_manager = EmbeddedA11yManagerLacros::GetInstance();
+  embedded_a11y_manager->SetReadingModeEnabled(true);
+  WaitForExtensionLoaded(profile,
+                         extension_misc::kEmbeddedA11yHelperExtensionId);
+
+  embedded_a11y_manager->SetReadingModeEnabled(false);
+  WaitForExtensionUnloaded(profile,
+                           extension_misc::kEmbeddedA11yHelperExtensionId);
+}
+
+IN_PROC_BROWSER_TEST_F(EmbeddedA11yManagerLacrosTest,
                        SwitchAccessAndSelectToSpeak) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   const auto& profiles = profile_manager->GetLoadedProfiles();
diff --git a/chrome/browser/lacros/prefs_ash_observer.cc b/chrome/browser/lacros/prefs_ash_observer.cc
index 8315e0bf..ef240a8 100644
--- a/chrome/browser/lacros/prefs_ash_observer.cc
+++ b/chrome/browser/lacros/prefs_ash_observer.cc
@@ -4,9 +4,13 @@
 
 #include "chrome/browser/lacros/prefs_ash_observer.h"
 
+#include <memory>
+
+#include "ash/constants/ash_pref_names.h"
 #include "base/functional/bind.h"
 #include "base/logging.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/mahi/mahi_web_contents_manager.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/crosapi/mojom/prefs.mojom.h"
@@ -34,6 +38,12 @@
       base::BindRepeating(
           &PrefsAshObserver::OnDnsOverHttpsEffectiveTemplatesChromeOSChanged,
           base::Unretained(this)));
+
+  mahi_prefs_observer_ = std::make_unique<CrosapiPrefObserver>(
+      crosapi::mojom::PrefPath::kMahiEnabled,
+      base::BindRepeating(&PrefsAshObserver::OnMahiEnabledChanged,
+                          base::Unretained(this)));
+
   // TODO(acostinas, b/328566515) Remove `deprecated_doh_templates_observer_` in
   // version 126. In the meantime, monitor the pref kDnsOverHttpsTemplates
   // (which is deprecated in Lacros) to support older version of Ash.
@@ -105,6 +115,15 @@
                           value.GetString());
 }
 
+void PrefsAshObserver::OnMahiEnabledChanged(base::Value value) {
+  if (!value.is_bool()) {
+    LOG(WARNING) << "Unexpected value type: "
+                 << base::Value::GetTypeName(value.type());
+    return;
+  }
+  mahi::MahiWebContentsManager::Get()->set_mahi_pref_lacros(value.GetBool());
+}
+
 void PrefsAshObserver::OnDeprecatedDnsOverHttpsTemplatesChanged(
     base::Value value) {
   if (effective_chromeos_secure_dns_settings_active_) {
diff --git a/chrome/browser/lacros/prefs_ash_observer.h b/chrome/browser/lacros/prefs_ash_observer.h
index 0b8cf9f..4694e18 100644
--- a/chrome/browser/lacros/prefs_ash_observer.h
+++ b/chrome/browser/lacros/prefs_ash_observer.h
@@ -35,6 +35,7 @@
 
   void OnDnsOverHttpsModeChanged(base::Value value);
   void OnDnsOverHttpsEffectiveTemplatesChromeOSChanged(base::Value value);
+  void OnMahiEnabledChanged(base::Value value);
   // TODO(acostinas, b/328566515) Remove monitoring of the
   // kDnsOverHttpsTemplates after version 126.
   void OnDeprecatedDnsOverHttpsTemplatesChanged(base::Value value);
@@ -67,6 +68,7 @@
   raw_ptr<PrefService> local_state_{nullptr};
   std::unique_ptr<CrosapiPrefObserver> doh_mode_observer_;
   std::unique_ptr<CrosapiPrefObserver> doh_templates_observer_;
+  std::unique_ptr<CrosapiPrefObserver> mahi_prefs_observer_;
   // Tracks whether the DoH template URI is set via the pref
   // kDnsOverHttpsTemplates (which is deprecated in Lacros) or via the new pref,
   // kDnsOverHttpsEffectiveTemplatesChromeOS.
diff --git a/chrome/browser/lens/core/mojom/lens.mojom b/chrome/browser/lens/core/mojom/lens.mojom
index 3df25220..8bcac61 100644
--- a/chrome/browser/lens/core/mojom/lens.mojom
+++ b/chrome/browser/lens/core/mojom/lens.mojom
@@ -5,6 +5,7 @@
 module lens.mojom;
 
 import "chrome/browser/lens/core/mojom/geometry.mojom";
+import "chrome/browser/lens/core/mojom/overlay_object.mojom";
 import "chrome/browser/lens/core/mojom/text.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 import "url/mojom/url.mojom";
@@ -37,6 +38,9 @@
 
 // WebUI page handler for request from Browser side. (C++ -> TypeScript)
 interface LensPage {
+  // Passes objects received from Lens to WebUI for rendering.
+  ObjectsReceived(array<OverlayObject> objects);
+
   // Passes Text received from Lens to WebUI for rendering.
   TextReceived(Text text);
 };
diff --git a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinator.java b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinator.java
index dfb5d35..5bc433d 100644
--- a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinator.java
+++ b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinator.java
@@ -45,13 +45,13 @@
 public class HomeModulesCoordinator implements ModuleDelegate, OnViewCreatedCallback {
     private final ModuleDelegateHost mModuleDelegateHost;
     private HomeModulesMediator mMediator;
-    private final SimpleRecyclerViewAdapter mAdapter;
     private final HomeModulesRecyclerView mRecyclerView;
     private final ModelList mModel;
     private final HomeModulesContextMenuManager mHomeModulesContextMenuManager;
     private final ObservableSupplier<Profile> mProfileSupplier;
     private final ModuleRegistry mModuleRegistry;
 
+    private SimpleRecyclerViewAdapter mAdapter;
     private CirclePagerIndicatorDecoration mPageIndicatorDecoration;
     private SnapHelper mSnapHelper;
     private boolean mIsSnapHelperAttached;
@@ -101,16 +101,12 @@
         mProfileSupplier = profileSupplier;
 
         mModel = new ModelList();
-        mAdapter = new SimpleRecyclerViewAdapter(mModel);
-
-        mModuleRegistry.registerAdapter(mAdapter, this::onViewCreated);
         mRecyclerView = parentView.findViewById(R.id.home_modules_recycler_view);
-
-        mRecyclerView.setAdapter(mAdapter);
         LinearLayoutManager linearLayoutManager =
                 new LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false);
         mRecyclerView.setLayoutManager(linearLayoutManager);
 
+        maybeSetUpAdapter();
         // Add pager indicator.
         setupRecyclerView(activity);
 
@@ -129,6 +125,15 @@
         mMediator = new HomeModulesMediator(mModel, moduleRegistry);
     }
 
+    // Creates an Adapter and attaches it to the recyclerview if it hasn't yet.
+    private void maybeSetUpAdapter() {
+        if (mAdapter != null) return;
+
+        mAdapter = new SimpleRecyclerViewAdapter(mModel);
+        mModuleRegistry.registerAdapter(mAdapter, this::onViewCreated);
+        mRecyclerView.setAdapter(mAdapter);
+    }
+
     private void setupRecyclerView(Activity activity) {
         boolean isTablet = DeviceFormFactor.isNonMultiDisplayContextOnTablet(activity);
         int startMargin = mModuleDelegateHost.getStartMargin();
@@ -301,6 +306,8 @@
         }
         mHasHomeModulesBeenScrolled = false;
         mMediator.hide();
+
+        destroyAdapter();
     }
 
     // ModuleDelegate implementation.
@@ -471,6 +478,8 @@
             return;
         }
 
+        maybeSetUpAdapter();
+
         mRecyclerView.addOnScrollListener(mOnScrollListener);
         mMediator.buildModulesAndShow(
                 moduleList,
@@ -553,6 +562,15 @@
         mRecyclerView.removeOnScrollListener(mOnScrollListener);
     }
 
+    private void destroyAdapter() {
+        if (mAdapter == null) return;
+
+        // Destroys and unattaches the adapter to allow recycling the views.
+        mRecyclerView.setAdapter(null);
+        mAdapter.destroy();
+        mAdapter = null;
+    }
+
     void setMediatorForTesting(HomeModulesMediator mediator) {
         mMediator = mediator;
     }
diff --git a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtils.java b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtils.java
index 7bf8a967..bf70da7 100644
--- a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtils.java
+++ b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtils.java
@@ -23,12 +23,12 @@
     @VisibleForTesting static final String HISTOGRAM_MAGIC_STACK_MODULE_CLICK = ".Module.Click.";
 
     @VisibleForTesting
-    static final String HISTOGRAM_MAGIC_STACK_MODULE_IMPRESSION = ".Module.TopImpression.";
+    static final String HISTOGRAM_MAGIC_STACK_MODULE_IMPRESSION = ".Module.TopImpressionV2";
 
-    @VisibleForTesting static final String HISTOGRAM_CONTEXT_MENU_SHOWN = ".ContextMenu.Shown.";
+    @VisibleForTesting static final String HISTOGRAM_CONTEXT_MENU_SHOWN = ".ContextMenu.ShownV2";
 
     @VisibleForTesting
-    static final String HISTOGRAM_CONTEXT_MENU_REMOVE_MODULE = ".ContextMenu.RemoveModule.";
+    static final String HISTOGRAM_CONTEXT_MENU_REMOVE_MODULE = ".ContextMenu.RemoveModuleV2";
 
     @VisibleForTesting
     static final String HISTOGRAM_CONTEXT_MENU_OPEN_CUSTOMIZE_SETTINGS =
@@ -42,7 +42,7 @@
             ".Module.FetchDataTimeoutDurationMs.";
 
     @VisibleForTesting
-    static final String HISTOGRAM_MODULE_FETCH_DATA_TIMEOUT_TYPE = ".Module.FetchDataTimeoutType.";
+    static final String HISTOGRAM_MODULE_FETCH_DATA_TIMEOUT_TYPE = ".Module.FetchDataTimeoutTypeV2";
 
     @VisibleForTesting
     static final String HISTOGRAM_MODULE_FETCH_DATA_FAILED_DURATION_MS =
diff --git a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java
index dd2990c..04fd77bf 100644
--- a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java
+++ b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -173,6 +174,20 @@
 
     @Test
     @SmallTest
+    @DisableFeatures({ChromeFeatureList.SEGMENTATION_PLATFORM_ANDROID_HOME_MODULE_RANKER})
+    public void testHide() {
+        mCoordinator = createCoordinator(/* skipInitProfile= */ false);
+        verify(mRecyclerView).setAdapter(notNull());
+
+        mCoordinator.hide();
+        verify(mRecyclerView).setAdapter(eq(null));
+
+        mCoordinator.show((isVisible) -> {});
+        verify(mRecyclerView, times(2)).setAdapter(notNull());
+    }
+
+    @Test
+    @SmallTest
     public void testDestroy() {
         setupAndVerifyTablets();
         assertTrue(DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity));
diff --git a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtilsUnitTest.java b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtilsUnitTest.java
index e9721e8..50e9583f 100644
--- a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtilsUnitTest.java
+++ b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtilsUnitTest.java
@@ -4,18 +4,6 @@
 
 package org.chromium.chrome.browser.magic_stack;
 
-import static org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils.HISTOGRAM_CONFIGURATION_TURN_OFF_MODULE;
-import static org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils.HISTOGRAM_CONFIGURATION_TURN_ON_MODULE;
-import static org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils.HISTOGRAM_FIRST_MODULE_SHOWN_DURATION_MS;
-import static org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils.HISTOGRAM_MAGIC_STACK_MODULE_BUILD;
-import static org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils.HISTOGRAM_MAGIC_STACK_MODULE_CLICK;
-import static org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils.HISTOGRAM_MODULE_FETCH_DATA_DURATION_MS;
-import static org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils.HISTOGRAM_MODULE_FETCH_DATA_FAILED_DURATION_MS;
-import static org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils.HISTOGRAM_MODULE_FETCH_DATA_TIMEOUT_DURATION_MS;
-import static org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils.HISTOGRAM_MODULE_FETCH_DATA_TIMEOUT_TYPE;
-import static org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils.HISTOGRAM_MODULE_PROFILE_READY_DELAY_MS;
-import static org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils.HISTOGRAM_MODULE_SEGMENTATION_FETCH_RANKING_DURATION_MS;
-
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -36,9 +24,7 @@
     public void testRecordModuleShown() {
         @HostSurface int hostSurface = HostSurface.START_SURFACE;
         @ModuleType int moduleType = ModuleType.SINGLE_TAB;
-        String histogramName =
-                "MagicStack.Clank.StartSurface"
-                        + HomeModulesMetricsUtils.HISTOGRAM_MAGIC_STACK_MODULE_IMPRESSION;
+        String histogramName = "MagicStack.Clank.StartSurface.Module.TopImpressionV2";
 
         var histogramWatcher = HistogramWatcher.newSingleRecordWatcher(histogramName, moduleType);
         HomeModulesMetricsUtils.recordModuleShown(hostSurface, moduleType);
@@ -50,9 +36,7 @@
     public void testRecordContextMenuShown() {
         @HostSurface int hostSurface = HostSurface.START_SURFACE;
         @ModuleType int moduleType = ModuleType.SINGLE_TAB;
-        String histogramName =
-                "MagicStack.Clank.StartSurface"
-                        + HomeModulesMetricsUtils.HISTOGRAM_CONTEXT_MENU_SHOWN;
+        String histogramName = "MagicStack.Clank.StartSurface.ContextMenu.ShownV2";
 
         var histogramWatcher = HistogramWatcher.newSingleRecordWatcher(histogramName, moduleType);
         HomeModulesMetricsUtils.recordContextMenuShown(hostSurface, moduleType);
@@ -64,9 +48,7 @@
     public void testRecordContextMenuRemoveModule() {
         @HostSurface int hostSurface = HostSurface.START_SURFACE;
         @ModuleType int moduleType = ModuleType.SINGLE_TAB;
-        String histogramName =
-                "MagicStack.Clank.StartSurface"
-                        + HomeModulesMetricsUtils.HISTOGRAM_CONTEXT_MENU_REMOVE_MODULE;
+        String histogramName = "MagicStack.Clank.StartSurface.ContextMenu.RemoveModuleV2";
 
         var histogramWatcher = HistogramWatcher.newSingleRecordWatcher(histogramName, moduleType);
         HomeModulesMetricsUtils.recordContextMenuRemoveModule(hostSurface, moduleType);
@@ -78,9 +60,7 @@
     public void testRecordContextMenuCustomizeSettings() {
         @HostSurface int hostSurface = HostSurface.START_SURFACE;
         @ModuleType int moduleType = ModuleType.SINGLE_TAB;
-        String histogramName =
-                "MagicStack.Clank.StartSurface"
-                        + HomeModulesMetricsUtils.HISTOGRAM_CONTEXT_MENU_OPEN_CUSTOMIZE_SETTINGS;
+        String histogramName = "MagicStack.Clank.StartSurface.ContextMenu.OpenCustomizeSettings";
 
         var histogramWatcher = HistogramWatcher.newSingleRecordWatcher(histogramName, moduleType);
         HomeModulesMetricsUtils.recordContextMenuCustomizeSettings(hostSurface, moduleType);
@@ -94,11 +74,7 @@
         @ModuleType int moduleType = ModuleType.SINGLE_TAB;
         int duration = 100;
 
-        StringBuilder builder = new StringBuilder();
-        builder.append("MagicStack.Clank.StartSurface");
-        builder.append(HISTOGRAM_MODULE_FETCH_DATA_DURATION_MS);
-        builder.append(HomeModulesMetricsUtils.getModuleName(moduleType));
-        String histogramName = builder.toString();
+        String histogramName = "MagicStack.Clank.StartSurface.Module.FetchDataDurationMs.SingleTab";
 
         var histogramWatcher =
                 HistogramWatcher.newBuilder().expectIntRecord(histogramName, duration).build();
@@ -113,11 +89,8 @@
         @ModuleType int moduleType = ModuleType.SINGLE_TAB;
         int duration = 100;
 
-        StringBuilder builder = new StringBuilder();
-        builder.append("MagicStack.Clank.StartSurface");
-        builder.append(HISTOGRAM_MODULE_FETCH_DATA_TIMEOUT_DURATION_MS);
-        builder.append(HomeModulesMetricsUtils.getModuleName(moduleType));
-        String histogramName = builder.toString();
+        String histogramName =
+                "MagicStack.Clank.StartSurface.Module.FetchDataTimeoutDurationMs.SingleTab";
 
         var histogramWatcher =
                 HistogramWatcher.newBuilder().expectIntRecord(histogramName, duration).build();
@@ -130,8 +103,7 @@
     public void testRecordFetchDataTimeoutType() {
         @HostSurface int hostSurface = HostSurface.START_SURFACE;
         @ModuleType int moduleType = ModuleType.SINGLE_TAB;
-        String histogramName =
-                "MagicStack.Clank.StartSurface" + HISTOGRAM_MODULE_FETCH_DATA_TIMEOUT_TYPE;
+        String histogramName = "MagicStack.Clank.StartSurface.Module.FetchDataTimeoutTypeV2";
 
         var histogramWatcher = HistogramWatcher.newSingleRecordWatcher(histogramName, moduleType);
         HomeModulesMetricsUtils.recordFetchDataTimeOutType(hostSurface, moduleType);
@@ -145,11 +117,8 @@
         @ModuleType int moduleType = ModuleType.SINGLE_TAB;
         int duration = 100;
 
-        StringBuilder builder = new StringBuilder();
-        builder.append("MagicStack.Clank.StartSurface");
-        builder.append(HISTOGRAM_MODULE_FETCH_DATA_FAILED_DURATION_MS);
-        builder.append(HomeModulesMetricsUtils.getModuleName(moduleType));
-        String histogramName = builder.toString();
+        String histogramName =
+                "MagicStack.Clank.StartSurface.Module.FetchDataFailedDurationMs.SingleTab";
 
         var histogramWatcher =
                 HistogramWatcher.newBuilder().expectIntRecord(histogramName, duration).build();
@@ -162,8 +131,7 @@
     public void testRecordFirstModuleShowDuration() {
         @HostSurface int hostSurface = HostSurface.START_SURFACE;
         int duration = 100;
-        String histogramName =
-                "MagicStack.Clank.StartSurface" + HISTOGRAM_FIRST_MODULE_SHOWN_DURATION_MS;
+        String histogramName = "MagicStack.Clank.StartSurface.Module.FirstModuleShownDurationMs";
 
         var histogramWatcher =
                 HistogramWatcher.newBuilder().expectIntRecord(histogramName, duration).build();
@@ -176,8 +144,7 @@
     public void testRecordProfileReadyDelay() {
         @HostSurface int hostSurface = HostSurface.START_SURFACE;
         int duration = 100;
-        String histogramName =
-                "MagicStack.Clank.StartSurface" + HISTOGRAM_MODULE_PROFILE_READY_DELAY_MS;
+        String histogramName = "MagicStack.Clank.StartSurface.Module.ProfileReadyDelayMs";
 
         var histogramWatcher =
                 HistogramWatcher.newBuilder().expectIntRecord(histogramName, duration).build();
@@ -191,8 +158,7 @@
         @HostSurface int hostSurface = HostSurface.START_SURFACE;
         int duration = 100;
         String histogramName =
-                "MagicStack.Clank.StartSurface"
-                        + HISTOGRAM_MODULE_SEGMENTATION_FETCH_RANKING_DURATION_MS;
+                "MagicStack.Clank.StartSurface.Segmentation.FetchRankingResultsDurationMs";
 
         var histogramWatcher =
                 HistogramWatcher.newBuilder().expectIntRecord(histogramName, duration).build();
@@ -207,13 +173,7 @@
         @ModuleType int moduleType = ModuleType.SINGLE_TAB;
         int modulePosition = 0;
 
-        StringBuilder builder = new StringBuilder();
-        builder.append("MagicStack.Clank.StartSurface");
-        builder.append(HISTOGRAM_MAGIC_STACK_MODULE_CLICK);
-        builder.append(HomeModulesMetricsUtils.getModuleName(moduleType));
-        builder.append(".");
-        builder.append(modulePosition);
-        String histogramName = builder.toString();
+        String histogramName = "MagicStack.Clank.StartSurface.Module.Click.SingleTab.0";
 
         var histogramWatcher = HistogramWatcher.newSingleRecordWatcher(histogramName, 1);
         HomeModulesMetricsUtils.recordModuleClickedPosition(
@@ -227,9 +187,7 @@
         @HostSurface int hostSurface = HostSurface.START_SURFACE;
         boolean isScrollable = true;
         boolean isScrolled = true;
-        String histogramName =
-                "MagicStack.Clank.StartSurface"
-                        + HomeModulesMetricsUtils.HISTOGRAM_MAGIC_STACK_SCROLLABLE_SCROLLED;
+        String histogramName = "MagicStack.Clank.StartSurface.Scrollable.Scrolled";
 
         var histogramWatcher = HistogramWatcher.newSingleRecordWatcher(histogramName, 1);
         HomeModulesMetricsUtils.recordHomeModulesScrollState(hostSurface, isScrollable, isScrolled);
@@ -241,14 +199,14 @@
     public void testRecordModuleToggledInConfiguration() {
         @ModuleType int moduleType = ModuleType.PRICE_CHANGE;
         boolean isEnabled = true;
-        String histogramName = "MagicStack.Clank." + HISTOGRAM_CONFIGURATION_TURN_ON_MODULE;
+        String histogramName = "MagicStack.Clank.Settings.TurnOnModule";
 
         var histogramWatcher = HistogramWatcher.newSingleRecordWatcher(histogramName, moduleType);
         HomeModulesMetricsUtils.recordModuleToggledInConfiguration(moduleType, isEnabled);
         histogramWatcher.assertExpected();
 
         isEnabled = false;
-        histogramName = "MagicStack.Clank." + HISTOGRAM_CONFIGURATION_TURN_OFF_MODULE;
+        histogramName = "MagicStack.Clank.Settings.TurnOffModule";
 
         histogramWatcher = HistogramWatcher.newSingleRecordWatcher(histogramName, moduleType);
         HomeModulesMetricsUtils.recordModuleToggledInConfiguration(moduleType, isEnabled);
@@ -262,13 +220,7 @@
         @ModuleType int moduleType = ModuleType.SINGLE_TAB;
         int modulePosition = 0;
 
-        StringBuilder builder = new StringBuilder();
-        builder.append("MagicStack.Clank.StartSurface");
-        builder.append(HISTOGRAM_MAGIC_STACK_MODULE_BUILD);
-        builder.append(HomeModulesMetricsUtils.getModuleName(moduleType));
-        builder.append(".");
-        builder.append(modulePosition);
-        String histogramName = builder.toString();
+        String histogramName = "MagicStack.Clank.StartSurface.Module.Build.SingleTab.0";
 
         var histogramWatcher = HistogramWatcher.newSingleRecordWatcher(histogramName, 1);
         HomeModulesMetricsUtils.recordModuleBuiltPosition(hostSurface, moduleType, modulePosition);
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager.cc b/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
index 8b84ade0..f32859d 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
@@ -1052,7 +1052,8 @@
     if (sink_capabilities.HasAll(info.required_capabilities))
       return info.app_id;
   }
-  NOTREACHED() << "Can't determine app ID from capabilities.";
+  DUMP_WILL_BE_NOTREACHED_NORETURN()
+      << "Can't determine app ID from capabilities.";
   return source.app_infos()[0].app_id;
 }
 
diff --git a/chrome/browser/metrics/ukm_browsertest.cc b/chrome/browser/metrics/ukm_browsertest.cc
index c6263a0..087f5aa4 100644
--- a/chrome/browser/metrics/ukm_browsertest.cc
+++ b/chrome/browser/metrics/ukm_browsertest.cc
@@ -71,6 +71,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #else
+#include "chrome/browser/flags/android/chrome_feature_list.h"
 #include "chrome/browser/flags/android/chrome_session_state.h"
 #include "chrome/browser/ui/android/tab_model/tab_model.h"
 #include "chrome/browser/ui/android/tab_model/tab_model_list.h"
@@ -354,6 +355,11 @@
     // would need to remove the pre-existing TabModel and add a new one.
     // Having an empty TabModelList allows us to simply add the appropriate
     // TabModel.
+    if (base::FeatureList::IsEnabled(chrome::android::kAndroidTabDeclutter)) {
+      EXPECT_EQ(2U, TabModelList::models().size());
+      TabModelList::RemoveTabModel(TabModelList::models()[1]);
+    }
+    EXPECT_EQ(1U, TabModelList::models().size());
     TabModelList::RemoveTabModel(TabModelList::models()[0]);
     EXPECT_EQ(0U, TabModelList::models().size());
   }
diff --git a/chrome/browser/nearby_sharing/common/BUILD.gn b/chrome/browser/nearby_sharing/common/BUILD.gn
index f03c9aa..91531aa 100644
--- a/chrome/browser/nearby_sharing/common/BUILD.gn
+++ b/chrome/browser/nearby_sharing/common/BUILD.gn
@@ -8,7 +8,6 @@
 
 source_set("common") {
   sources = [
-    "nearby_share_enums.h",
     "nearby_share_features.cc",
     "nearby_share_features.h",
     "nearby_share_prefs.cc",
diff --git a/chrome/browser/nearby_sharing/common/nearby_share_enums.h b/chrome/browser/nearby_sharing/common/nearby_share_enums.h
deleted file mode 100644
index c9274f4..0000000
--- a/chrome/browser/nearby_sharing/common/nearby_share_enums.h
+++ /dev/null
@@ -1,26 +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.
-
-#ifndef CHROME_BROWSER_NEARBY_SHARING_COMMON_NEARBY_SHARE_ENUMS_H_
-#define CHROME_BROWSER_NEARBY_SHARING_COMMON_NEARBY_SHARE_ENUMS_H_
-
-#include "chromeos/ash/services/nearby/public/mojom/nearby_share_settings.mojom.h"
-
-// Represents the advertising bluetooth power for Nearby Connections.
-enum class PowerLevel {
-  kUnknown = 0,
-  kLowPower = 1,
-  kMediumPower = 2,
-  kHighPower = 3,
-  kMaxValue = kHighPower
-};
-
-// TODO(https://crbug.com/1106369): these names are too generic for the global
-// namespace
-using DataUsage = nearby_share::mojom::DataUsage;
-using Visibility = nearby_share::mojom::Visibility;
-using FastInitiationNotificationState =
-    nearby_share::mojom::FastInitiationNotificationState;
-
-#endif  // CHROME_BROWSER_NEARBY_SHARING_COMMON_NEARBY_SHARE_ENUMS_H_
diff --git a/chrome/browser/nearby_sharing/common/nearby_share_prefs.cc b/chrome/browser/nearby_sharing/common/nearby_share_prefs.cc
index 28e668b..9c103a8 100644
--- a/chrome/browser/nearby_sharing/common/nearby_share_prefs.cc
+++ b/chrome/browser/nearby_sharing/common/nearby_share_prefs.cc
@@ -8,7 +8,7 @@
 
 #include "base/files/file_path.h"
 #include "base/time/time.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
+#include "chromeos/ash/services/nearby/public/mojom/nearby_share_settings.mojom.h"
 #include "components/prefs/pref_registry.h"
 #include "components/prefs/pref_registry_simple.h"
 
@@ -67,15 +67,15 @@
   registry->RegisterIntegerPref(
       prefs::kNearbySharingFastInitiationNotificationStatePrefName,
       /*default_value=*/static_cast<int>(
-          FastInitiationNotificationState::kEnabled));
+          nearby_share::mojom::FastInitiationNotificationState::kEnabled));
   registry->RegisterBooleanPref(prefs::kNearbySharingOnboardingCompletePrefName,
                                 /*default_value=*/false);
-  registry->RegisterIntegerPref(
-      prefs::kNearbySharingBackgroundVisibilityName,
-      /*default_value=*/static_cast<int>(Visibility::kUnknown));
-  registry->RegisterIntegerPref(
-      prefs::kNearbySharingDataUsageName,
-      /*default_value=*/static_cast<int>(DataUsage::kWifiOnly));
+  registry->RegisterIntegerPref(prefs::kNearbySharingBackgroundVisibilityName,
+                                /*default_value=*/static_cast<int>(
+                                    nearby_share::mojom::Visibility::kUnknown));
+  registry->RegisterIntegerPref(prefs::kNearbySharingDataUsageName,
+                                /*default_value=*/static_cast<int>(
+                                    nearby_share::mojom::DataUsage::kWifiOnly));
   registry->RegisterStringPref(prefs::kNearbySharingContactUploadHashPrefName,
                                /*default_value=*/std::string());
   registry->RegisterStringPref(prefs::kNearbySharingDeviceIdPrefName,
diff --git a/chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner_feature_usage_metrics.cc b/chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner_feature_usage_metrics.cc
index 10e2d76..55b5257 100644
--- a/chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner_feature_usage_metrics.cc
+++ b/chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner_feature_usage_metrics.cc
@@ -4,10 +4,10 @@
 
 #include "chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner_feature_usage_metrics.h"
 
-#include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
 #include "chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner.h"
 #include "chrome/browser/nearby_sharing/nearby_share_feature_status.h"
+#include "chromeos/ash/services/nearby/public/mojom/nearby_share_settings.mojom.h"
 #include "components/prefs/pref_service.h"
 
 namespace {
@@ -37,9 +37,11 @@
   return IsEligible() &&
          GetNearbyShareEnabledState(pref_service_) !=
              NearbyShareEnabledState::kDisallowedByPolicy &&
-         static_cast<FastInitiationNotificationState>(pref_service_->GetInteger(
-             prefs::kNearbySharingFastInitiationNotificationStatePrefName)) ==
-             FastInitiationNotificationState::kEnabled;
+         static_cast<nearby_share::mojom::FastInitiationNotificationState>(
+             pref_service_->GetInteger(
+                 prefs::
+                     kNearbySharingFastInitiationNotificationStatePrefName)) ==
+             nearby_share::mojom::FastInitiationNotificationState::kEnabled;
 }
 
 void FastInitiationScannerFeatureUsageMetrics::SetBluetoothAdapter(
diff --git a/chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner_feature_usage_metrics_unittest.cc b/chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner_feature_usage_metrics_unittest.cc
index 11e9e8c..57aebbc 100644
--- a/chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner_feature_usage_metrics_unittest.cc
+++ b/chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner_feature_usage_metrics_unittest.cc
@@ -10,9 +10,9 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
 #include "chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner.h"
+#include "chromeos/ash/services/nearby/public/mojom/nearby_share_settings.mojom.h"
 #include "components/prefs/testing_pref_service.h"
 #include "device/bluetooth/bluetooth_adapter_factory.h"
 #include "device/bluetooth/test/mock_bluetooth_adapter.h"
@@ -122,7 +122,8 @@
 TEST_F(FastInitiationScannerFeatureUsageMetricsTest, NotEnabled_Pref) {
   pref_service_.SetInteger(
       prefs::kNearbySharingFastInitiationNotificationStatePrefName,
-      static_cast<int>(FastInitiationNotificationState::kDisabledByUser));
+      static_cast<int>(nearby_share::mojom::FastInitiationNotificationState::
+                           kDisabledByUser));
   FastInitiationScannerFeatureUsageMetrics feature_usage_metrics(
       &pref_service_);
   feature_usage_metrics.SetBluetoothAdapter(mock_bluetooth_adapter_);
diff --git a/chrome/browser/nearby_sharing/nearby_connection_impl.cc b/chrome/browser/nearby_sharing/nearby_connection_impl.cc
index 5035422..86f50a16 100644
--- a/chrome/browser/nearby_sharing/nearby_connection_impl.cc
+++ b/chrome/browser/nearby_sharing/nearby_connection_impl.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/nearby_sharing/nearby_connection_impl.h"
 
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "crypto/random.h"
 
 NearbyConnectionImpl::NearbyConnectionImpl(
diff --git a/chrome/browser/nearby_sharing/nearby_connections_manager_impl.cc b/chrome/browser/nearby_sharing/nearby_connections_manager_impl.cc
index 0bc24d1..6105391 100644
--- a/chrome/browser/nearby_sharing/nearby_connections_manager_impl.cc
+++ b/chrome/browser/nearby_sharing/nearby_connections_manager_impl.cc
@@ -13,8 +13,8 @@
 #include "base/unguessable_token.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
 #include "chrome/browser/nearby_sharing/constants.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chrome/services/sharing/nearby/common/nearby_features.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/components/nearby/presence/conversions/proto_conversions.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_presence.mojom.h"
@@ -30,14 +30,15 @@
 const nearby::connections::mojom::Strategy kStrategy =
     nearby::connections::mojom::Strategy::kP2pPointToPoint;
 
-bool ShouldUseInternet(DataUsage data_usage, PowerLevel power_level) {
+bool ShouldUseInternet(NearbyConnectionsManager::DataUsage data_usage,
+                       NearbyConnectionsManager::PowerLevel power_level) {
   // We won't use internet if the user requested we don't.
-  if (data_usage == DataUsage::kOffline) {
+  if (data_usage == NearbyConnectionsManager::DataUsage::kOffline) {
     return false;
   }
 
   // We won't use internet in a low power mode.
-  if (power_level == PowerLevel::kLowPower) {
+  if (power_level == NearbyConnectionsManager::PowerLevel::kLowPower) {
     return false;
   }
 
@@ -52,7 +53,7 @@
   }
 
   // If the user wants to limit Wi-Fi, then don't use it on metered networks.
-  if (data_usage == DataUsage::kWifiOnly &&
+  if (data_usage == NearbyConnectionsManager::DataUsage::kWifiOnly &&
       net::NetworkChangeNotifier::GetConnectionCost() ==
           net::NetworkChangeNotifier::CONNECTION_COST_METERED) {
     CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
@@ -65,12 +66,14 @@
   return true;
 }
 
-bool ShouldEnableWebRtc(DataUsage data_usage, PowerLevel power_level) {
+bool ShouldEnableWebRtc(NearbyConnectionsManager::DataUsage data_usage,
+                        NearbyConnectionsManager::PowerLevel power_level) {
   return base::FeatureList::IsEnabled(features::kNearbySharingWebRtc) &&
          ShouldUseInternet(data_usage, power_level);
 }
 
-bool ShouldEnableWifiLan(DataUsage data_usage, PowerLevel power_level) {
+bool ShouldEnableWifiLan(NearbyConnectionsManager::DataUsage data_usage,
+                         NearbyConnectionsManager::PowerLevel power_level) {
   if (!base::FeatureList::IsEnabled(features::kNearbySharingWifiLan)) {
     return false;
   }
@@ -238,8 +241,8 @@
 void NearbyConnectionsManagerImpl::StartAdvertising(
     std::vector<uint8_t> endpoint_info,
     IncomingConnectionListener* listener,
-    PowerLevel power_level,
-    DataUsage data_usage,
+    NearbyConnectionsManager::PowerLevel power_level,
+    NearbyConnectionsManager::DataUsage data_usage,
     ConnectionsCallback callback) {
   DCHECK(listener);
   DCHECK(!incoming_connection_listener_);
@@ -251,16 +254,19 @@
     return;
   }
 
-  bool is_high_power = power_level == PowerLevel::kHighPower;
+  bool is_high_power =
+      power_level == NearbyConnectionsManager::PowerLevel::kHighPower;
   bool use_ble = features::IsNearbyBleV2Enabled() || !is_high_power;
   auto allowed_mediums = MediumSelection::New(
       /*bluetooth=*/is_high_power, /*ble=*/use_ble,
       // Using kHighPower here rather than power_level to signal that power
       // level isn't a factor when deciding whether or not to allow WebRTC
       // upgrades from this advertisement.
-      ShouldEnableWebRtc(data_usage, PowerLevel::kHighPower),
+      ShouldEnableWebRtc(data_usage,
+                         NearbyConnectionsManager::PowerLevel::kHighPower),
       /*wifi_lan=*/
-      ShouldEnableWifiLan(data_usage, PowerLevel::kHighPower) &&
+      ShouldEnableWifiLan(data_usage,
+                          NearbyConnectionsManager::PowerLevel::kHighPower) &&
           kIsWifiLanAdvertisingSupported);
   CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
       << __func__ << ": " << "is_high_power=" << (is_high_power ? "yes" : "no")
@@ -321,7 +327,7 @@
 
 void NearbyConnectionsManagerImpl::StartDiscovery(
     DiscoveryListener* listener,
-    DataUsage data_usage,
+    NearbyConnectionsManager::DataUsage data_usage,
     ConnectionsCallback callback) {
   DCHECK(listener);
   DCHECK(!discovery_listener_);
@@ -336,9 +342,12 @@
   auto allowed_mediums = MediumSelection::New(
       /*bluetooth=*/true,
       /*ble=*/true,
-      /*webrtc=*/ShouldEnableWebRtc(data_usage, PowerLevel::kHighPower),
+      /*webrtc=*/
+      ShouldEnableWebRtc(data_usage,
+                         NearbyConnectionsManager::PowerLevel::kHighPower),
       /*wifi_lan=*/
-      ShouldEnableWifiLan(data_usage, PowerLevel::kHighPower) &&
+      ShouldEnableWifiLan(data_usage,
+                          NearbyConnectionsManager::PowerLevel::kHighPower) &&
           kIsWifiLanDiscoverySupported);
   CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
       << __func__ << ": " << "data_usage=" << data_usage
@@ -380,7 +389,7 @@
     std::vector<uint8_t> endpoint_info,
     const std::string& endpoint_id,
     std::optional<std::vector<uint8_t>> bluetooth_mac_address,
-    DataUsage data_usage,
+    NearbyConnectionsManager::DataUsage data_usage,
     NearbyConnectionCallback callback) {
   // TODO(https://crbug.com/1177088): Determine if we should attempt to bind to
   // process.
@@ -395,8 +404,12 @@
 
   auto allowed_mediums = MediumSelection::New(
       /*bluetooth=*/true,
-      /*ble=*/false, ShouldEnableWebRtc(data_usage, PowerLevel::kHighPower),
-      /*wifi_lan=*/ShouldEnableWifiLan(data_usage, PowerLevel::kHighPower));
+      /*ble=*/false,
+      ShouldEnableWebRtc(data_usage,
+                         NearbyConnectionsManager::PowerLevel::kHighPower),
+      /*wifi_lan=*/
+      ShouldEnableWifiLan(data_usage,
+                          NearbyConnectionsManager::PowerLevel::kHighPower));
   CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
       << __func__ << ": " << "data_usage=" << data_usage
       << ", allowed_mediums=" << MediumSelectionToString(*allowed_mediums);
@@ -678,7 +691,7 @@
 
 void NearbyConnectionsManagerImpl::ConnectV3(
     nearby::presence::PresenceDevice remote_presence_device,
-    DataUsage data_usage,
+    NearbyConnectionsManager::DataUsage data_usage,
     NearbyConnectionCallback callback) {
   nearby::connections::mojom::NearbyConnections* nearby_connections =
       GetNearbyConnections();
@@ -687,8 +700,12 @@
   // TODO(b/287340241): Enable BLE connections as an allowed medium.
   auto allowed_mediums = MediumSelection::New(
       /*bluetooth=*/true,
-      /*ble=*/false, ShouldEnableWebRtc(data_usage, PowerLevel::kHighPower),
-      /*wifi_lan=*/ShouldEnableWifiLan(data_usage, PowerLevel::kHighPower));
+      /*ble=*/false,
+      ShouldEnableWebRtc(data_usage,
+                         NearbyConnectionsManager::PowerLevel::kHighPower),
+      /*wifi_lan=*/
+      ShouldEnableWifiLan(data_usage,
+                          NearbyConnectionsManager::PowerLevel::kHighPower));
   CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
       << __func__ << ": " << "data_usage=" << data_usage
       << ", allowed_mediums=" << MediumSelectionToString(*allowed_mediums);
diff --git a/chrome/browser/nearby_sharing/nearby_connections_manager_impl.h b/chrome/browser/nearby_sharing/nearby_connections_manager_impl.h
index f113dc7..e2e6f95 100644
--- a/chrome/browser/nearby_sharing/nearby_connections_manager_impl.h
+++ b/chrome/browser/nearby_sharing/nearby_connections_manager_impl.h
@@ -5,8 +5,6 @@
 #ifndef CHROME_BROWSER_NEARBY_SHARING_NEARBY_CONNECTIONS_MANAGER_IMPL_H_
 #define CHROME_BROWSER_NEARBY_SHARING_NEARBY_CONNECTIONS_MANAGER_IMPL_H_
 
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
-
 #include <memory>
 
 #include "base/containers/flat_map.h"
@@ -17,6 +15,7 @@
 #include "base/timer/timer.h"
 #include "chrome/browser/nearby_sharing/nearby_connection_impl.h"
 #include "chrome/browser/nearby_sharing/nearby_file_handler.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/components/nearby/presence/nearby_presence_service.h"
 #include "chromeos/ash/services/nearby/public/cpp/nearby_process_manager.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_connections.mojom.h"
@@ -45,7 +44,7 @@
   void Shutdown() override;
   void StartAdvertising(std::vector<uint8_t> endpoint_info,
                         IncomingConnectionListener* listener,
-                        PowerLevel power_level,
+                        NearbyConnectionsManager::PowerLevel power_level,
                         DataUsage data_usage,
                         ConnectionsCallback callback) override;
   void StopAdvertising(ConnectionsCallback callback) override;
diff --git a/chrome/browser/nearby_sharing/nearby_connections_manager_impl_unittest.cc b/chrome/browser/nearby_sharing/nearby_connections_manager_impl_unittest.cc
index 2b26a12..690e985 100644
--- a/chrome/browser/nearby_sharing/nearby_connections_manager_impl_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_connections_manager_impl_unittest.cc
@@ -279,7 +279,7 @@
 
   void StartDiscovery(
       mojo::Remote<EndpointDiscoveryListener>& listener_remote,
-      DataUsage data_usage,
+      NearbyConnectionsManager::DataUsage data_usage,
       testing::NiceMock<MockDiscoveryListener>& discovery_listener) {
     EXPECT_CALL(nearby_connections_, StartDiscovery)
         .WillOnce([&listener_remote, this](
@@ -343,7 +343,8 @@
         });
     nearby_connections_manager_->StartAdvertising(
         local_endpoint_info, &incoming_connection_listener,
-        PowerLevel::kHighPower, DataUsage::kOnline, std::move(callback));
+        NearbyConnectionsManager::PowerLevel::kHighPower,
+        NearbyConnectionsManager::DataUsage::kOnline, std::move(callback));
     run_loop.Run();
   }
 
@@ -381,7 +382,8 @@
     NearbyConnection* nearby_connection;
     nearby_connections_manager_->Connect(
         local_endpoint_info, kRemoteEndpointId,
-        /*bluetooth_mac_address=*/std::nullopt, DataUsage::kOffline,
+        /*bluetooth_mac_address=*/std::nullopt,
+        NearbyConnectionsManager::DataUsage::kOffline,
         base::BindLambdaForTesting([&](NearbyConnection* connection) {
           nearby_connection = connection;
         }));
@@ -547,7 +549,7 @@
     base::RunLoop accept_or_reject_run_loop;
     NearbyConnection* nearby_connection;
     nearby_connections_manager_->ConnectV3(
-        remote_presence_device, DataUsage::kOffline,
+        remote_presence_device, NearbyConnectionsManager::DataUsage::kOffline,
         base::BindLambdaForTesting([&](NearbyConnection* connection) {
           nearby_connection = connection;
 
@@ -602,7 +604,8 @@
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   bool should_use_web_rtc_ = true;
   bool should_use_wifilan_ = false;
-  DataUsage default_data_usage_ = DataUsage::kWifiOnly;
+  NearbyConnectionsManager::DataUsage default_data_usage_ =
+      NearbyConnectionsManager::DataUsage::kWifiOnly;
   std::unique_ptr<net::test::MockNetworkChangeNotifier> network_notifier_ =
       net::test::MockNetworkChangeNotifier::Create();
   base::ScopedDisallowBlocking disallow_blocking_;
@@ -714,8 +717,11 @@
 /******************************************************************************/
 // Begin: NearbyConnectionsManagerImplTestConnectionMediums
 /******************************************************************************/
-using ConnectionMediumsTestParam = std::
-    tuple<DataUsage, net::NetworkChangeNotifier::ConnectionType, bool, bool>;
+using ConnectionMediumsTestParam =
+    std::tuple<NearbyConnectionsManager::DataUsage,
+               net::NetworkChangeNotifier::ConnectionType,
+               bool,
+               bool>;
 class NearbyConnectionsManagerImplTestConnectionMediums
     : public NearbyConnectionsManagerImplTest,
       public testing::WithParamInterface<ConnectionMediumsTestParam> {};
@@ -723,7 +729,7 @@
 TEST_P(NearbyConnectionsManagerImplTestConnectionMediums,
        RequestConnection_MediumSelection) {
   const ConnectionMediumsTestParam& param = GetParam();
-  DataUsage data_usage = std::get<0>(param);
+  NearbyConnectionsManager::DataUsage data_usage = std::get<0>(param);
   net::NetworkChangeNotifier::ConnectionType connection_type =
       std::get<1>(param);
   bool is_webrtc_enabled = std::get<2>(GetParam());
@@ -747,9 +753,9 @@
   network_notifier_->SetConnectionType(connection_type);
   network_notifier_->SetUseDefaultConnectionCostImplementation(true);
   bool should_use_internet =
-      data_usage != DataUsage::kOffline &&
+      data_usage != NearbyConnectionsManager::DataUsage::kOffline &&
       connection_type != net::NetworkChangeNotifier::CONNECTION_NONE &&
-      (data_usage != DataUsage::kWifiOnly ||
+      (data_usage != NearbyConnectionsManager::DataUsage::kWifiOnly ||
        (net::NetworkChangeNotifier::GetConnectionCost() !=
         net::NetworkChangeNotifier::CONNECTION_COST_METERED));
   bool is_connection_wifi_or_ethernet =
@@ -800,9 +806,9 @@
     NearbyConnectionsManagerImplTestConnectionMediums,
     NearbyConnectionsManagerImplTestConnectionMediums,
     testing::Combine(
-        testing::Values(DataUsage::kWifiOnly,
-                        DataUsage::kOffline,
-                        DataUsage::kOnline),
+        testing::Values(NearbyConnectionsManager::DataUsage::kWifiOnly,
+                        NearbyConnectionsManager::DataUsage::kOffline,
+                        NearbyConnectionsManager::DataUsage::kOnline),
         testing::Values(net::NetworkChangeNotifier::CONNECTION_NONE,
                         net::NetworkChangeNotifier::CONNECTION_WIFI,
                         net::NetworkChangeNotifier::CONNECTION_3G),
@@ -860,9 +866,9 @@
             run_loop.Quit();
           });
 
-  nearby_connections_manager_->Connect(local_endpoint_info, kRemoteEndpointId,
-                                       GetParam().bluetooth_mac_address,
-                                       DataUsage::kOffline, base::DoNothing());
+  nearby_connections_manager_->Connect(
+      local_endpoint_info, kRemoteEndpointId, GetParam().bluetooth_mac_address,
+      NearbyConnectionsManager::DataUsage::kOffline, base::DoNothing());
 
   run_loop.Run();
 }
@@ -1362,7 +1368,8 @@
   NearbyConnection* nearby_connection = nullptr;
   nearby_connections_manager_->Connect(
       local_endpoint_info, kRemoteEndpointId,
-      /*bluetooth_mac_address=*/std::nullopt, DataUsage::kOffline,
+      /*bluetooth_mac_address=*/std::nullopt,
+      NearbyConnectionsManager::DataUsage::kOffline,
       base::BindLambdaForTesting([&](NearbyConnection* connection) {
         nearby_connection = connection;
         run_loop.Quit();
@@ -1771,8 +1778,8 @@
 /******************************************************************************/
 // Begin: NearbyConnectionsManagerImplTestMediums
 /******************************************************************************/
-using MediumsTestParam = std::tuple<PowerLevel,
-                                    DataUsage,
+using MediumsTestParam = std::tuple<NearbyConnectionsManager::PowerLevel,
+                                    NearbyConnectionsManager::DataUsage,
                                     net::NetworkChangeNotifier::ConnectionType,
                                     bool,
                                     bool>;
@@ -1782,8 +1789,8 @@
 
 TEST_P(NearbyConnectionsManagerImplTestMediums, StartAdvertising_Options) {
   const MediumsTestParam& param = GetParam();
-  PowerLevel power_level = std::get<0>(param);
-  DataUsage data_usage = std::get<1>(param);
+  NearbyConnectionsManager::PowerLevel power_level = std::get<0>(param);
+  NearbyConnectionsManager::DataUsage data_usage = std::get<1>(param);
   net::NetworkChangeNotifier::ConnectionType connection_type =
       std::get<2>(param);
   bool is_webrtc_enabled = std::get<3>(GetParam());
@@ -1796,13 +1803,15 @@
   network_notifier_->SetConnectionType(connection_type);
   network_notifier_->SetUseDefaultConnectionCostImplementation(true);
   should_use_web_rtc_ =
-      is_webrtc_enabled && data_usage != DataUsage::kOffline &&
+      is_webrtc_enabled &&
+      data_usage != NearbyConnectionsManager::DataUsage::kOffline &&
       connection_type != net::NetworkChangeNotifier::CONNECTION_NONE &&
-      (data_usage != DataUsage::kWifiOnly ||
+      (data_usage != NearbyConnectionsManager::DataUsage::kWifiOnly ||
        (net::NetworkChangeNotifier::GetConnectionCost() !=
         net::NetworkChangeNotifier::CONNECTION_COST_METERED));
 
-  bool is_high_power = power_level == PowerLevel::kHighPower;
+  bool is_high_power =
+      power_level == NearbyConnectionsManager::PowerLevel::kHighPower;
   bool use_ble = is_ble_v2_enabled || !is_high_power;
 
   // TODO(crbug.com/1129069): Update when WiFi LAN is supported.
@@ -1852,10 +1861,11 @@
     NearbyConnectionsManagerImplTestMediums,
     NearbyConnectionsManagerImplTestMediums,
     testing::Combine(
-        testing::Values(PowerLevel::kLowPower, PowerLevel::kHighPower),
-        testing::Values(DataUsage::kWifiOnly,
-                        DataUsage::kOffline,
-                        DataUsage::kOnline),
+        testing::Values(NearbyConnectionsManager::PowerLevel::kLowPower,
+                        NearbyConnectionsManager::PowerLevel::kHighPower),
+        testing::Values(NearbyConnectionsManager::DataUsage::kWifiOnly,
+                        NearbyConnectionsManager::DataUsage::kOffline,
+                        NearbyConnectionsManager::DataUsage::kOnline),
         testing::Values(net::NetworkChangeNotifier::CONNECTION_NONE,
                         net::NetworkChangeNotifier::CONNECTION_WIFI,
                         net::NetworkChangeNotifier::CONNECTION_3G),
@@ -1934,7 +1944,8 @@
   NearbyConnection* nearby_connection;
   nearby_connections_manager_->Connect(
       local_endpoint_info, kRemoteEndpointId,
-      /*bluetooth_mac_address=*/std::nullopt, DataUsage::kOffline,
+      /*bluetooth_mac_address=*/std::nullopt,
+      NearbyConnectionsManager::DataUsage::kOffline,
       base::BindLambdaForTesting([&](NearbyConnection* connection) {
         nearby_connection = connection;
         connect_run_loop.Quit();
@@ -2068,8 +2079,9 @@
             request_connection_run_loop.Quit();
           });
 
-  nearby_connections_manager_->ConnectV3(presence_device, DataUsage::kOffline,
-                                         base::DoNothing());
+  nearby_connections_manager_->ConnectV3(
+      presence_device, NearbyConnectionsManager::DataUsage::kOffline,
+      base::DoNothing());
   request_connection_run_loop.Run();
 }
 
@@ -2167,8 +2179,9 @@
             request_connection_run_loop.Quit();
           });
 
-  nearby_connections_manager_->ConnectV3(presence_device, DataUsage::kOffline,
-                                         base::DoNothing());
+  nearby_connections_manager_->ConnectV3(
+      presence_device, NearbyConnectionsManager::DataUsage::kOffline,
+      base::DoNothing());
   request_connection_run_loop.Run();
 }
 
@@ -2196,8 +2209,9 @@
 
   ash::nearby::presence::mojom::PresenceDevicePtr presence_device_mojom =
       BuildPresenceMojomDevice(presence_device);
-  nearby_connections_manager_->ConnectV3(presence_device, DataUsage::kOffline,
-                                         base::DoNothing());
+  nearby_connections_manager_->ConnectV3(
+      presence_device, NearbyConnectionsManager::DataUsage::kOffline,
+      base::DoNothing());
   request_connection_run_loop.Run();
 
   testing::NiceMock<MockBandwidthUpgradeListener> bandwidth_listener;
diff --git a/chrome/browser/nearby_sharing/nearby_notification_manager.cc b/chrome/browser/nearby_sharing/nearby_notification_manager.cc
index 51848ba4..07e1c05 100644
--- a/chrome/browser/nearby_sharing/nearby_notification_manager.cc
+++ b/chrome/browser/nearby_sharing/nearby_notification_manager.cc
@@ -27,7 +27,6 @@
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/download/download_prefs.h"
 #include "chrome/browser/image_decoder/image_decoder.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_resource_getter.h"
@@ -759,11 +758,12 @@
 }
 
 bool ShouldShowNearbyVisibilityReminderNotification(PrefService* pref_service) {
-  Visibility visibility = static_cast<Visibility>(
-      pref_service->GetInteger(prefs::kNearbySharingBackgroundVisibilityName));
+  nearby_share::mojom::Visibility visibility =
+      static_cast<nearby_share::mojom::Visibility>(pref_service->GetInteger(
+          prefs::kNearbySharingBackgroundVisibilityName));
 
-  return visibility == Visibility::kAllContacts ||
-         visibility == Visibility::kSelectedContacts;
+  return visibility == nearby_share::mojom::Visibility::kAllContacts ||
+         visibility == nearby_share::mojom::Visibility::kSelectedContacts;
 }
 
 void UpdateNearbyDeviceTryingToShareDismissedTime(PrefService* pref_service) {
diff --git a/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc b/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
index a226f66..ea74734 100644
--- a/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/download/download_core_service_factory.h"
 #include "chrome/browser/download/download_core_service_impl.h"
 #include "chrome/browser/download/download_prefs.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_resource_getter.h"
@@ -2171,43 +2170,44 @@
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kIsNameEnabled});
-  pref_service_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                           static_cast<int>(Visibility::kAllContacts));
+  pref_service_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kAllContacts));
 
-    manager()->ShowVisibilityReminder();
-    std::vector<message_center::Notification> notifications =
-        GetDisplayedNotifications();
-    ASSERT_EQ(1u, notifications.size());
-    const message_center::Notification& notification = notifications[0];
-    EXPECT_EQ(message_center::NOTIFICATION_TYPE_SIMPLE, notification.type());
-    EXPECT_EQ(l10n_util::GetStringUTF16(
-                  IDS_NEARBY_NOTIFICATION_VISIBILITY_REMINDER_TITLE),
-              notification.title());
-    EXPECT_EQ(l10n_util::GetStringUTF16(
-                  IDS_NEARBY_NOTIFICATION_VISIBILITY_REMINDER_MESSAGE),
-              notification.message());
-    EXPECT_TRUE(notification.icon().IsEmpty());
-    EXPECT_EQ(GURL(), notification.origin_url());
-    EXPECT_FALSE(notification.never_timeout());
-    EXPECT_FALSE(notification.renotify());
-    EXPECT_EQ(&kNearbyShareIcon, &notification.vector_small_image());
-    EXPECT_EQ(l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_SOURCE),
-              notification.display_source());
-    EXPECT_EQ(2u, notification.buttons().size());
+  manager()->ShowVisibilityReminder();
+  std::vector<message_center::Notification> notifications =
+      GetDisplayedNotifications();
+  ASSERT_EQ(1u, notifications.size());
+  const message_center::Notification& notification = notifications[0];
+  EXPECT_EQ(message_center::NOTIFICATION_TYPE_SIMPLE, notification.type());
+  EXPECT_EQ(l10n_util::GetStringUTF16(
+                IDS_NEARBY_NOTIFICATION_VISIBILITY_REMINDER_TITLE),
+            notification.title());
+  EXPECT_EQ(l10n_util::GetStringUTF16(
+                IDS_NEARBY_NOTIFICATION_VISIBILITY_REMINDER_MESSAGE),
+            notification.message());
+  EXPECT_TRUE(notification.icon().IsEmpty());
+  EXPECT_EQ(GURL(), notification.origin_url());
+  EXPECT_FALSE(notification.never_timeout());
+  EXPECT_FALSE(notification.renotify());
+  EXPECT_EQ(&kNearbyShareIcon, &notification.vector_small_image());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_SOURCE),
+            notification.display_source());
+  EXPECT_EQ(2u, notification.buttons().size());
 
-    std::vector<std::u16string> expected_button_titles;
-    expected_button_titles.push_back(l10n_util::GetStringUTF16(
-        IDS_NEARBY_NOTIFICATION_GO_TO_SETTINGS_ACTION));
-    expected_button_titles.push_back(
-        l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_DISMISS_ACTION));
+  std::vector<std::u16string> expected_button_titles;
+  expected_button_titles.push_back(
+      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_GO_TO_SETTINGS_ACTION));
+  expected_button_titles.push_back(
+      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_DISMISS_ACTION));
 
-    const std::vector<message_center::ButtonInfo>& buttons =
-        notification.buttons();
-    ASSERT_EQ(expected_button_titles.size(), buttons.size());
+  const std::vector<message_center::ButtonInfo>& buttons =
+      notification.buttons();
+  ASSERT_EQ(expected_button_titles.size(), buttons.size());
 
-    for (size_t i = 0; i < expected_button_titles.size(); ++i) {
-      EXPECT_EQ(expected_button_titles[i], buttons[i].title);
-    }
+  for (size_t i = 0; i < expected_button_titles.size(); ++i) {
+    EXPECT_EQ(expected_button_titles[i], buttons[i].title);
+  }
 }
 
 TEST_P(NearbyNotificationManagerTest,
@@ -2216,8 +2216,9 @@
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kIsNameEnabled},
       /*disabled_features=*/{});
-  pref_service_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                           static_cast<int>(Visibility::kAllContacts));
+  pref_service_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kAllContacts));
 
   manager()->ShowVisibilityReminder();
   std::vector<message_center::Notification> notifications =
@@ -2261,8 +2262,9 @@
 }
 
 TEST_P(NearbyNotificationManagerTest, ShowVisibilityReminder_Hidden_Mode) {
-  pref_service_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                           static_cast<int>(Visibility::kNoOne));
+  pref_service_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kNoOne));
   manager()->ShowVisibilityReminder();
   std::vector<message_center::Notification> notifications =
       GetDisplayedNotifications();
@@ -2271,72 +2273,76 @@
 
 TEST_P(NearbyNotificationManagerTest,
        ShowVisibilityReminder_Notification_Clicked) {
-  pref_service_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                           static_cast<int>(Visibility::kSelectedContacts));
+  pref_service_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kSelectedContacts));
 
-    manager()->ShowVisibilityReminder();
-    std::vector<message_center::Notification> notifications =
-        GetDisplayedNotifications();
-    ASSERT_EQ(1u, notifications.size());
-    EXPECT_CALL(*settings_opener_, ShowSettingsPage(_, _));
-    notification_tester_->SimulateClick(NotificationHandler::Type::NEARBY_SHARE,
-                                        notifications[0].id(),
-                                        /*action_index=*/std::optional<int>(),
-                                        /*reply=*/std::nullopt);
+  manager()->ShowVisibilityReminder();
+  std::vector<message_center::Notification> notifications =
+      GetDisplayedNotifications();
+  ASSERT_EQ(1u, notifications.size());
+  EXPECT_CALL(*settings_opener_, ShowSettingsPage(_, _));
+  notification_tester_->SimulateClick(NotificationHandler::Type::NEARBY_SHARE,
+                                      notifications[0].id(),
+                                      /*action_index=*/std::optional<int>(),
+                                      /*reply=*/std::nullopt);
 
-    // Notification should be closed.
-    EXPECT_EQ(0u, GetDisplayedNotifications().size());
+  // Notification should be closed.
+  EXPECT_EQ(0u, GetDisplayedNotifications().size());
 }
 
 TEST_P(NearbyNotificationManagerTest, ShowVisibilityReminder_Settings_Clicked) {
-  pref_service_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                           static_cast<int>(Visibility::kAllContacts));
+  pref_service_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kAllContacts));
 
-    manager()->ShowVisibilityReminder();
-    std::vector<message_center::Notification> notifications =
-        GetDisplayedNotifications();
-    ASSERT_EQ(1u, notifications.size());
-    EXPECT_CALL(*settings_opener_, ShowSettingsPage(_, _));
-    notification_tester_->SimulateClick(NotificationHandler::Type::NEARBY_SHARE,
-                                        notifications[0].id(),
-                                        /*action_index=*/0,
-                                        /*reply=*/std::nullopt);
+  manager()->ShowVisibilityReminder();
+  std::vector<message_center::Notification> notifications =
+      GetDisplayedNotifications();
+  ASSERT_EQ(1u, notifications.size());
+  EXPECT_CALL(*settings_opener_, ShowSettingsPage(_, _));
+  notification_tester_->SimulateClick(NotificationHandler::Type::NEARBY_SHARE,
+                                      notifications[0].id(),
+                                      /*action_index=*/0,
+                                      /*reply=*/std::nullopt);
 
-    // Notification should be closed.
-    EXPECT_EQ(0u, GetDisplayedNotifications().size());
+  // Notification should be closed.
+  EXPECT_EQ(0u, GetDisplayedNotifications().size());
 }
 
 TEST_P(NearbyNotificationManagerTest, ShowVisibilityReminder_Dismiss_Clicked) {
-  pref_service_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                           static_cast<int>(Visibility::kAllContacts));
+  pref_service_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kAllContacts));
 
-    manager()->ShowVisibilityReminder();
-    std::vector<message_center::Notification> notifications =
-        GetDisplayedNotifications();
-    ASSERT_EQ(1u, notifications.size());
-    notification_tester_->SimulateClick(NotificationHandler::Type::NEARBY_SHARE,
-                                        notifications[0].id(),
-                                        /*action_index=*/1,
-                                        /*reply=*/std::nullopt);
+  manager()->ShowVisibilityReminder();
+  std::vector<message_center::Notification> notifications =
+      GetDisplayedNotifications();
+  ASSERT_EQ(1u, notifications.size());
+  notification_tester_->SimulateClick(NotificationHandler::Type::NEARBY_SHARE,
+                                      notifications[0].id(),
+                                      /*action_index=*/1,
+                                      /*reply=*/std::nullopt);
 
-    // Notification should be closed.
-    EXPECT_EQ(0u, GetDisplayedNotifications().size());
+  // Notification should be closed.
+  EXPECT_EQ(0u, GetDisplayedNotifications().size());
 }
 
 TEST_P(NearbyNotificationManagerTest,
        ShowVisibilityReminder_Notification_Closed) {
-  pref_service_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                           static_cast<int>(Visibility::kAllContacts));
+  pref_service_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kAllContacts));
 
-    manager()->ShowVisibilityReminder();
-    std::vector<message_center::Notification> notifications =
-        GetDisplayedNotifications();
-    ASSERT_EQ(1u, notifications.size());
-    notification_tester_->RemoveNotification(
-        NotificationHandler::Type::NEARBY_SHARE, notifications[0].id(), true);
+  manager()->ShowVisibilityReminder();
+  std::vector<message_center::Notification> notifications =
+      GetDisplayedNotifications();
+  ASSERT_EQ(1u, notifications.size());
+  notification_tester_->RemoveNotification(
+      NotificationHandler::Type::NEARBY_SHARE, notifications[0].id(), true);
 
-    // Notification should be closed.
-    EXPECT_EQ(0u, GetDisplayedNotifications().size());
+  // Notification should be closed.
+  EXPECT_EQ(0u, GetDisplayedNotifications().size());
 }
 
 TEST_P(NearbyNotificationManagerTest, ConnectionRequest_SelfShare) {
diff --git a/chrome/browser/nearby_sharing/nearby_share_settings.cc b/chrome/browser/nearby_sharing/nearby_share_settings.cc
index 718088c..a16b22a6 100644
--- a/chrome/browser/nearby_sharing/nearby_share_settings.cc
+++ b/chrome/browser/nearby_sharing/nearby_share_settings.cc
@@ -6,7 +6,6 @@
 
 #include "base/metrics/histogram_functions.h"
 #include "base/values.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -63,10 +62,11 @@
   return pref_service_->GetBoolean(prefs::kNearbySharingEnabledPrefName);
 }
 
-FastInitiationNotificationState
+nearby_share::mojom::FastInitiationNotificationState
 NearbyShareSettings::GetFastInitiationNotificationState() const {
-  return static_cast<FastInitiationNotificationState>(pref_service_->GetInteger(
-      prefs::kNearbySharingFastInitiationNotificationStatePrefName));
+  return static_cast<nearby_share::mojom::FastInitiationNotificationState>(
+      pref_service_->GetInteger(
+          prefs::kNearbySharingFastInitiationNotificationStatePrefName));
 }
 
 void NearbyShareSettings::SetIsFastInitiationHardwareSupported(
@@ -86,12 +86,12 @@
   return local_device_data_manager_->GetDeviceName();
 }
 
-DataUsage NearbyShareSettings::GetDataUsage() const {
+NearbyShareSettings::DataUsage NearbyShareSettings::GetDataUsage() const {
   return static_cast<DataUsage>(
       pref_service_->GetInteger(prefs::kNearbySharingDataUsageName));
 }
 
-Visibility NearbyShareSettings::GetVisibility() const {
+nearby_share::mojom::Visibility NearbyShareSettings::GetVisibility() const {
   int visibility_int =
       pref_service_->GetInteger(prefs::kNearbySharingBackgroundVisibilityName);
 
@@ -99,12 +99,14 @@
   // to disabled, `visibility_int` will have a greater enum value than
   // `Visibility::kMaxValue`, causing UB. In this case, the visibility is set to
   // kNoOne instead.
-  if (visibility_int > static_cast<int>(Visibility::kMaxValue)) {
-    pref_service_->SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                              static_cast<int>(Visibility::kNoOne));
-    return Visibility::kNoOne;
+  if (visibility_int >
+      static_cast<int>(nearby_share::mojom::Visibility::kMaxValue)) {
+    pref_service_->SetInteger(
+        prefs::kNearbySharingBackgroundVisibilityName,
+        static_cast<int>(nearby_share::mojom::Visibility::kNoOne));
+    return nearby_share::mojom::Visibility::kNoOne;
   } else {
-    return static_cast<Visibility>(visibility_int);
+    return static_cast<nearby_share::mojom::Visibility>(visibility_int);
   }
 }
 
@@ -139,7 +141,8 @@
 }
 
 void NearbyShareSettings::GetFastInitiationNotificationState(
-    base::OnceCallback<void(FastInitiationNotificationState)> callback) {
+    base::OnceCallback<
+        void(nearby_share::mojom::FastInitiationNotificationState)> callback) {
   std::move(callback).Run(GetFastInitiationNotificationState());
 }
 
@@ -151,16 +154,16 @@
 void NearbyShareSettings::SetEnabled(bool enabled) {
   DCHECK(!enabled || IsOnboardingComplete());
   pref_service_->SetBoolean(prefs::kNearbySharingEnabledPrefName, enabled);
-  if (enabled && GetVisibility() == Visibility::kUnknown) {
+  if (enabled && GetVisibility() == nearby_share::mojom::Visibility::kUnknown) {
     CD_LOG(ERROR, Feature::NS)
         << "Nearby Share enabled with visibility unset. Setting "
            "visibility to kNoOne.";
-    SetVisibility(Visibility::kNoOne);
+    SetVisibility(nearby_share::mojom::Visibility::kNoOne);
   }
 }
 
 void NearbyShareSettings::SetFastInitiationNotificationState(
-    FastInitiationNotificationState state) {
+    nearby_share::mojom::FastInitiationNotificationState state) {
   pref_service_->SetInteger(
       prefs::kNearbySharingFastInitiationNotificationStatePrefName,
       static_cast<int>(state));
@@ -261,7 +264,8 @@
 }
 
 void NearbyShareSettings::OnFastInitiationNotificationStatePrefChanged() {
-  FastInitiationNotificationState state = GetFastInitiationNotificationState();
+  nearby_share::mojom::FastInitiationNotificationState state =
+      GetFastInitiationNotificationState();
   for (auto& remote : observers_set_) {
     remote->OnFastInitiationNotificationStateChanged(state);
   }
@@ -275,7 +279,7 @@
 }
 
 void NearbyShareSettings::OnVisibilityPrefChanged() {
-  Visibility visibility = GetVisibility();
+  nearby_share::mojom::Visibility visibility = GetVisibility();
   for (auto& remote : observers_set_) {
     remote->OnVisibilityChanged(visibility);
   }
@@ -306,10 +310,11 @@
   // If the user explicitly disabled notifications, toggling the Nearby Share
   // feature does not re-enable the notification sub-feature.
   if (GetFastInitiationNotificationState() ==
-      FastInitiationNotificationState::kDisabledByUser) {
+      nearby_share::mojom::FastInitiationNotificationState::kDisabledByUser) {
     return;
   }
   SetFastInitiationNotificationState(
-      enabled ? FastInitiationNotificationState::kEnabled
-              : FastInitiationNotificationState::kDisabledByFeature);
+      enabled ? nearby_share::mojom::FastInitiationNotificationState::kEnabled
+              : nearby_share::mojom::FastInitiationNotificationState::
+                    kDisabledByFeature);
 }
diff --git a/chrome/browser/nearby_sharing/nearby_share_settings.h b/chrome/browser/nearby_sharing/nearby_share_settings.h
index b8c7a33..e77739d 100644
--- a/chrome/browser/nearby_sharing/nearby_share_settings.h
+++ b/chrome/browser/nearby_sharing/nearby_share_settings.h
@@ -46,6 +46,7 @@
 class NearbyShareSettings : public nearby_share::mojom::NearbyShareSettings,
                             public NearbyShareLocalDeviceDataManager::Observer {
  public:
+  using DataUsage = nearby_share::mojom::DataUsage;
   NearbyShareSettings(
       PrefService* pref_service_,
       NearbyShareLocalDeviceDataManager* local_device_data_manager);
diff --git a/chrome/browser/nearby_sharing/nearby_share_settings_unittest.cc b/chrome/browser/nearby_sharing/nearby_share_settings_unittest.cc
index 130605b..3b37649 100644
--- a/chrome/browser/nearby_sharing/nearby_share_settings_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_share_settings_unittest.cc
@@ -10,7 +10,6 @@
 #include "base/feature_list.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
 #include "chrome/browser/nearby_sharing/local_device_data/fake_nearby_share_local_device_data_manager.h"
@@ -204,7 +203,7 @@
 
   // Set explicitly disabled by user.
   settings()->SetFastInitiationNotificationState(
-      FastInitiationNotificationState::kDisabledByUser);
+      nearby_share::mojom::FastInitiationNotificationState::kDisabledByUser);
   FlushMojoMessages();
   EXPECT_EQ(
       nearby_share::mojom::FastInitiationNotificationState::kDisabledByUser,
@@ -294,8 +293,9 @@
 
 TEST_F(NearbyShareSettingsTest, GetAndSetDataUsage) {
   EXPECT_EQ(nearby_share::mojom::DataUsage::kUnknown, observer_.data_usage_);
-  settings()->SetDataUsage(DataUsage::kOffline);
-  EXPECT_EQ(DataUsage::kOffline, settings()->GetDataUsage());
+  settings()->SetDataUsage(nearby_share::mojom::DataUsage::kOffline);
+  EXPECT_EQ(nearby_share::mojom::DataUsage::kOffline,
+            settings()->GetDataUsage());
   FlushMojoMessages();
   EXPECT_EQ(nearby_share::mojom::DataUsage::kOffline, observer_.data_usage_);
 
@@ -307,8 +307,9 @@
 
 TEST_F(NearbyShareSettingsTest, GetAndSetVisibility) {
   EXPECT_EQ(nearby_share::mojom::Visibility::kUnknown, observer_.visibility_);
-  settings()->SetVisibility(Visibility::kNoOne);
-  EXPECT_EQ(Visibility::kNoOne, settings()->GetVisibility());
+  settings()->SetVisibility(nearby_share::mojom::Visibility::kNoOne);
+  EXPECT_EQ(nearby_share::mojom::Visibility::kNoOne,
+            settings()->GetVisibility());
   FlushMojoMessages();
   EXPECT_EQ(nearby_share::mojom::Visibility::kNoOne, observer_.visibility_);
 
diff --git a/chrome/browser/nearby_sharing/nearby_share_transfer_profiler.h b/chrome/browser/nearby_sharing/nearby_share_transfer_profiler.h
index b9ba3cac..1641a81 100644
--- a/chrome/browser/nearby_sharing/nearby_share_transfer_profiler.h
+++ b/chrome/browser/nearby_sharing/nearby_share_transfer_profiler.h
@@ -11,7 +11,7 @@
 #include "base/containers/flat_map.h"
 #include "base/time/time.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 
 struct SenderData {
   std::optional<base::TimeTicks> discovered_time = std::nullopt;
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_factory.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_factory.cc
index 0e17f50a..ccdc0a2 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_factory.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_factory.cc
@@ -19,11 +19,11 @@
 #include "chrome/browser/nearby_sharing/nearby_connections_manager_impl.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service_impl.h"
 #include "chrome/browser/nearby_sharing/power_client_chromeos.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chrome/browser/nearby_sharing/wifi_network_configuration/wifi_network_configuration_handler.h"
 #include "chrome/browser/notifications/notification_display_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "components/cross_device/logging/logging.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_registry_simple.h"
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
index e46b548..5bfee34 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
@@ -29,7 +29,6 @@
 #include "chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.h"
 #include "chrome/browser/nearby_sharing/certificates/nearby_share_encrypted_metadata_key.h"
 #include "chrome/browser/nearby_sharing/client/nearby_share_client_impl.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
 #include "chrome/browser/nearby_sharing/constants.h"
@@ -42,7 +41,6 @@
 #include "chrome/browser/nearby_sharing/nearby_share_metrics.h"
 #include "chrome/browser/nearby_sharing/nearby_share_transfer_profiler.h"
 #include "chrome/browser/nearby_sharing/paired_key_verification_runner.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chrome/browser/nearby_sharing/share_target.h"
 #include "chrome/browser/nearby_sharing/transfer_metadata.h"
 #include "chrome/browser/nearby_sharing/transfer_metadata_builder.h"
@@ -53,6 +51,7 @@
 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
 #include "chrome/services/sharing/public/cpp/advertisement.h"
 #include "chrome/services/sharing/public/cpp/conversions.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_decoder.mojom.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_share_target_types.mojom.h"
@@ -139,15 +138,15 @@
   }
 }
 
-std::string PowerLevelToString(PowerLevel level) {
+std::string PowerLevelToString(NearbyConnectionsManager::PowerLevel level) {
   switch (level) {
-    case PowerLevel::kLowPower:
+    case NearbyConnectionsManager::PowerLevel::kLowPower:
       return "LOW_POWER";
-    case PowerLevel::kMediumPower:
+    case NearbyConnectionsManager::PowerLevel::kMediumPower:
       return "MEDIUM_POWER";
-    case PowerLevel::kHighPower:
+    case NearbyConnectionsManager::PowerLevel::kHighPower:
       return "HIGH_POWER";
-    case PowerLevel::kUnknown:
+    case NearbyConnectionsManager::PowerLevel::kUnknown:
       return "UNKNOWN";
   }
 }
@@ -286,13 +285,13 @@
   Callback callback_;
 };
 
-bool isVisibleForAdvertising(Visibility visibility) {
-  if (visibility == Visibility::kYourDevices &&
+bool isVisibleForAdvertising(nearby_share::mojom::Visibility visibility) {
+  if (visibility == nearby_share::mojom::Visibility::kYourDevices &&
       features::IsSelfShareEnabled()) {
     return true;
   }
-  return visibility == Visibility::kAllContacts ||
-         visibility == Visibility::kSelectedContacts;
+  return visibility == nearby_share::mojom::Visibility::kAllContacts ||
+         visibility == nearby_share::mojom::Visibility::kSelectedContacts;
 }
 
 }  // namespace
@@ -1168,7 +1167,7 @@
   is_receiving_files_ = false;
   is_sending_files_ = false;
   is_connecting_ = false;
-  advertising_power_level_ = PowerLevel::kUnknown;
+  advertising_power_level_ = NearbyConnectionsManager::PowerLevel::kUnknown;
 
   process_shutdown_pending_timer_.Stop();
   certificate_download_during_discovery_timer_.Stop();
@@ -1370,7 +1369,7 @@
 }
 
 void NearbySharingServiceImpl::OnFastInitiationNotificationStateChanged(
-    FastInitiationNotificationState state) {
+    nearby_share::mojom::FastInitiationNotificationState state) {
   CD_LOG(VERBOSE, Feature::NS)
       << __func__ << ": Fast Initiation Notification state: " << state;
   // Runs through a series of checks to determine if background scanning should
@@ -1385,14 +1384,16 @@
   // TODO(vecore): handle device name change
 }
 
-void NearbySharingServiceImpl::OnDataUsageChanged(DataUsage data_usage) {
+void NearbySharingServiceImpl::OnDataUsageChanged(
+    nearby_share::mojom::DataUsage data_usage) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   CD_LOG(INFO, Feature::NS)
       << __func__ << ": Nearby sharing data usage changed to " << data_usage;
   StopAdvertisingAndInvalidateSurfaceState();
 }
 
-void NearbySharingServiceImpl::OnVisibilityChanged(Visibility new_visibility) {
+void NearbySharingServiceImpl::OnVisibilityChanged(
+    nearby_share::mojom::Visibility new_visibility) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   CD_LOG(INFO, Feature::NS)
       << __func__ << ": Nearby sharing visibility changed to "
@@ -1556,7 +1557,8 @@
   }
 }
 
-bool NearbySharingServiceImpl::IsVisibleInBackground(Visibility visibility) {
+bool NearbySharingServiceImpl::IsVisibleInBackground(
+    nearby_share::mojom::Visibility visibility) {
   return isVisibleForAdvertising(visibility);
 }
 
@@ -1951,7 +1953,8 @@
   }
 
   // We're currently advertising.
-  if (advertising_power_level_ != PowerLevel::kUnknown) {
+  if (advertising_power_level_ !=
+      NearbyConnectionsManager::PowerLevel::kUnknown) {
     return false;
   }
 
@@ -2207,18 +2210,19 @@
 
   process_shutdown_pending_timer_.Stop();
 
-  PowerLevel power_level;
+  NearbyConnectionsManager::PowerLevel power_level;
   if (!foreground_receive_callbacks_.empty()) {
-    power_level = PowerLevel::kHighPower;
+    power_level = NearbyConnectionsManager::PowerLevel::kHighPower;
     // TODO(crbug/1100367) handle fast init
     // } else if (isFastInitDeviceNearby) {
-    //   power_level = PowerLevel::kMediumPower;
+    //   power_level = NearbyConnectionsManager::PowerLevel::kMediumPower;
   } else {
-    power_level = PowerLevel::kLowPower;
+    power_level = NearbyConnectionsManager::PowerLevel::kLowPower;
   }
 
-  DataUsage data_usage = settings_.GetDataUsage();
-  if (advertising_power_level_ != PowerLevel::kUnknown) {
+  nearby_share::mojom::DataUsage data_usage = settings_.GetDataUsage();
+  if (advertising_power_level_ !=
+      NearbyConnectionsManager::PowerLevel::kUnknown) {
     if (power_level == advertising_power_level_) {
       CD_LOG(VERBOSE, Feature::NS)
           << __func__ << ": Ignoring, already advertising with power level "
@@ -2286,7 +2290,8 @@
 }
 
 void NearbySharingServiceImpl::StopAdvertising() {
-  if (advertising_power_level_ == PowerLevel::kUnknown) {
+  if (advertising_power_level_ ==
+      NearbyConnectionsManager::PowerLevel::kUnknown) {
     CD_LOG(VERBOSE, Feature::NS)
         << __func__ << ": Not currently advertising, ignoring.";
     return;
@@ -2308,7 +2313,7 @@
   // with contact-based enabled), StartAdvertising will be called
   // immediately after StopAdvertising and will fail if the power level
   // indicates already advertising.
-  advertising_power_level_ = PowerLevel::kUnknown;
+  advertising_power_level_ = NearbyConnectionsManager::PowerLevel::kUnknown;
 }
 
 void NearbySharingServiceImpl::StartScanning() {
@@ -2374,7 +2379,8 @@
 }
 
 void NearbySharingServiceImpl::StopAdvertisingAndInvalidateSurfaceState() {
-  if (advertising_power_level_ != PowerLevel::kUnknown) {
+  if (advertising_power_level_ !=
+      NearbyConnectionsManager::PowerLevel::kUnknown) {
     StopAdvertising();
   }
 
@@ -2409,7 +2415,7 @@
   }
 
   if (settings_.GetFastInitiationNotificationState() !=
-      FastInitiationNotificationState::kEnabled) {
+      nearby_share::mojom::FastInitiationNotificationState::kEnabled) {
     CD_LOG(VERBOSE, Feature::NS)
         << __func__
         << ": Stopping background scanning; fast initiation "
@@ -2473,7 +2479,8 @@
     return;
   }
 
-  if (advertising_power_level_ == PowerLevel::kHighPower) {
+  if (advertising_power_level_ ==
+      NearbyConnectionsManager::PowerLevel::kHighPower) {
     CD_LOG(VERBOSE, Feature::NS)
         << __func__
         << ": Stopping background scanning because we're already "
@@ -3536,7 +3543,8 @@
 
   bool restrict_to_contacts =
       features::IsRestrictToContactsEnabled() && share_target.is_incoming &&
-      advertising_power_level_ != PowerLevel::kHighPower;
+      advertising_power_level_ !=
+          NearbyConnectionsManager::PowerLevel::kHighPower;
   share_target_info->set_key_verification_runner(
       std::make_unique<PairedKeyVerificationRunner>(
           share_target, endpoint_id, *token, share_target_info->connection(),
@@ -3599,7 +3607,8 @@
       transfer_profiler_->OnPairedKeyHandshakeComplete(
           info->endpoint_id().value());
 
-      if (advertising_power_level_ == PowerLevel::kHighPower) {
+      if (advertising_power_level_ ==
+          NearbyConnectionsManager::PowerLevel::kHighPower) {
         // Upgrade bandwidth if advertising at high-visibility. Bandwidth
         // upgrades may expose stable identifiers, but this isn't a concern
         // here because high-visibility already leaks the device name.
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h
index 99ab787e..8aaf3991 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_NEARBY_SHARING_NEARBY_SHARING_SERVICE_IMPL_H_
 
 #include <stdint.h>
+
 #include <memory>
 #include <string>
 #include <utility>
@@ -26,7 +27,6 @@
 #include "chrome/browser/nearby_sharing/attachment.h"
 #include "chrome/browser/nearby_sharing/attachment_info.h"
 #include "chrome/browser/nearby_sharing/client/nearby_share_http_notifier.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
 #include "chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner_feature_usage_metrics.h"
 #include "chrome/browser/nearby_sharing/incoming_frames_reader.h"
 #include "chrome/browser/nearby_sharing/incoming_share_target_info.h"
@@ -45,11 +45,11 @@
 #include "chrome/browser/nearby_sharing/nearby_sharing_service.h"
 #include "chrome/browser/nearby_sharing/outgoing_share_target_info.h"
 #include "chrome/browser/nearby_sharing/power_client.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chrome/browser/nearby_sharing/share_target.h"
 #include "chrome/browser/nearby_sharing/transfer_metadata.h"
 #include "chrome/browser/nearby_sharing/wifi_network_configuration/wifi_network_configuration_handler.h"
 #include "chrome/services/sharing/public/proto/wire_format.pb.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/components/nearby/presence/nearby_presence_service.h"
 #include "chromeos/ash/services/nearby/public/cpp/nearby_process_manager.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_decoder_types.mojom.h"
@@ -219,7 +219,7 @@
 
   base::ObserverList<TransferUpdateCallback>& GetReceiveCallbacksFromState(
       ReceiveSurfaceState state);
-  bool IsVisibleInBackground(Visibility visibility);
+  bool IsVisibleInBackground(nearby_share::mojom::Visibility visibility);
   const std::optional<std::vector<uint8_t>> CreateEndpointInfo(
       const std::optional<std::string>& device_name);
   void GetBluetoothAdapter();
@@ -554,7 +554,8 @@
 
   // The current advertising power level. PowerLevel::kUnknown while not
   // advertising.
-  PowerLevel advertising_power_level_ = PowerLevel::kUnknown;
+  NearbyConnectionsManager::PowerLevel advertising_power_level_ =
+      NearbyConnectionsManager::PowerLevel::kUnknown;
   // True if we are currently scanning for remote devices.
   bool is_scanning_ = false;
   // True if we're currently sending or receiving a file.
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
index 8535b30..71860c6 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
@@ -45,8 +45,6 @@
 #include "chrome/browser/nearby_sharing/nearby_share_feature_status.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
 #include "chrome/browser/nearby_sharing/power_client.h"
-#include "chrome/browser/nearby_sharing/public/cpp/fake_nearby_connections_manager.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chrome/browser/nearby_sharing/wifi_network_configuration/fake_wifi_network_configuration_handler.h"
 #include "chrome/browser/notifications/notification_display_service_factory.h"
 #include "chrome/browser/notifications/notification_display_service_tester.h"
@@ -57,6 +55,8 @@
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "chromeos/ash/components/feature_usage/feature_usage_metrics.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/fake_nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/services/nearby/public/cpp/mock_nearby_process_manager.h"
 #include "chromeos/ash/services/nearby/public/cpp/mock_nearby_sharing_decoder.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
@@ -2233,7 +2233,7 @@
       &callback, NearbySharingService::ReceiveSurfaceState::kForeground);
   EXPECT_EQ(result, NearbySharingService::StatusCodes::kOk);
   EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising());
-  EXPECT_EQ(PowerLevel::kHighPower,
+  EXPECT_EQ(NearbyConnectionsManager::PowerLevel::kHighPower,
             fake_nearby_connections_manager_->advertising_power_level());
   ASSERT_TRUE(fake_nearby_connections_manager_->advertising_endpoint_info());
   auto advertisement = GetCurrentAdvertisement();
@@ -2257,7 +2257,7 @@
       &callback, NearbySharingService::ReceiveSurfaceState::kForeground);
   EXPECT_EQ(result, NearbySharingService::StatusCodes::kOk);
   EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising());
-  EXPECT_EQ(PowerLevel::kHighPower,
+  EXPECT_EQ(NearbyConnectionsManager::PowerLevel::kHighPower,
             fake_nearby_connections_manager_->advertising_power_level());
   ASSERT_TRUE(fake_nearby_connections_manager_->advertising_endpoint_info());
   auto advertisement = GetCurrentAdvertisement();
@@ -2277,14 +2277,15 @@
        BackgroundRegisterReceiveSurfaceIsAdvertisingSelectedContacts) {
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
   SetVisibility(nearby_share::mojom::Visibility::kSelectedContacts);
-  prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kSelectedContacts));
+  prefs_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kSelectedContacts));
   MockTransferUpdateCallback callback;
   NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface(
       &callback, NearbySharingService::ReceiveSurfaceState::kBackground);
   EXPECT_EQ(result, NearbySharingService::StatusCodes::kOk);
   EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising());
-  EXPECT_EQ(PowerLevel::kLowPower,
+  EXPECT_EQ(NearbyConnectionsManager::PowerLevel::kLowPower,
             fake_nearby_connections_manager_->advertising_power_level());
   ASSERT_TRUE(fake_nearby_connections_manager_->advertising_endpoint_info());
   auto advertisement = GetCurrentAdvertisement();
@@ -2416,21 +2417,21 @@
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
 
   prefs_.SetInteger(prefs::kNearbySharingDataUsageName,
-                    static_cast<int>(DataUsage::kOffline));
+                    static_cast<int>(nearby_share::mojom::DataUsage::kOffline));
   service_->FlushMojoForTesting();
   MockTransferUpdateCallback callback;
   NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface(
       &callback, NearbySharingService::ReceiveSurfaceState::kForeground);
   EXPECT_EQ(result, NearbySharingService::StatusCodes::kOk);
   EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising());
-  EXPECT_EQ(DataUsage::kOffline,
+  EXPECT_EQ(nearby_share::mojom::DataUsage::kOffline,
             fake_nearby_connections_manager_->advertising_data_usage());
 
   prefs_.SetInteger(prefs::kNearbySharingDataUsageName,
-                    static_cast<int>(DataUsage::kOnline));
+                    static_cast<int>(nearby_share::mojom::DataUsage::kOnline));
   service_->FlushMojoForTesting();
   EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising());
-  EXPECT_EQ(DataUsage::kOnline,
+  EXPECT_EQ(nearby_share::mojom::DataUsage::kOnline,
             fake_nearby_connections_manager_->advertising_data_usage());
 }
 
@@ -2438,8 +2439,9 @@
     NearbySharingServiceImplTest,
     UnregisterForegroundReceiveSurfaceVisibilityAllContactsRestartAdvertising) {
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
-  prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kAllContacts));
+  prefs_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kAllContacts));
   service_->FlushMojoForTesting();
 
   // Register both foreground and background receive surfaces
@@ -2617,7 +2619,7 @@
        ForegroundReceiveSurfaceNoOneVisibilityIsAdvertising) {
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
   prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kNoOne));
+                    static_cast<int>(nearby_share::mojom::Visibility::kNoOne));
   MockTransferUpdateCallback callback;
   NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface(
       &callback, NearbySharingService::ReceiveSurfaceState::kForeground);
@@ -2629,7 +2631,7 @@
        BackgroundReceiveSurfaceNoOneVisibilityNotAdvertising) {
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
   prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kNoOne));
+                    static_cast<int>(nearby_share::mojom::Visibility::kNoOne));
   service_->FlushMojoForTesting();
   MockTransferUpdateCallback callback;
   NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface(
@@ -2642,8 +2644,9 @@
 TEST_P(NearbySharingServiceImplTest,
        BackgroundReceiveSurfaceVisibilityToNoOneStopsAdvertising) {
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
-  prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kSelectedContacts));
+  prefs_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kSelectedContacts));
   service_->FlushMojoForTesting();
   MockTransferUpdateCallback callback;
   NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface(
@@ -2652,7 +2655,7 @@
   EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising());
 
   prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kNoOne));
+                    static_cast<int>(nearby_share::mojom::Visibility::kNoOne));
   service_->FlushMojoForTesting();
   EXPECT_FALSE(fake_nearby_connections_manager_->IsAdvertising());
   EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown());
@@ -2662,7 +2665,7 @@
        BackgroundReceiveSurfaceVisibilityToSelectedStartsAdvertising) {
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
   prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kNoOne));
+                    static_cast<int>(nearby_share::mojom::Visibility::kNoOne));
   service_->FlushMojoForTesting();
   MockTransferUpdateCallback callback;
   NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface(
@@ -2671,8 +2674,9 @@
   EXPECT_FALSE(fake_nearby_connections_manager_->IsAdvertising());
   EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown());
 
-  prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kSelectedContacts));
+  prefs_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kSelectedContacts));
   service_->FlushMojoForTesting();
   EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising());
 }
@@ -2680,8 +2684,9 @@
 TEST_P(NearbySharingServiceImplTest,
        ForegroundReceiveSurfaceSelectedContactsVisibilityIsAdvertising) {
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
-  prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kSelectedContacts));
+  prefs_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kSelectedContacts));
   MockTransferUpdateCallback callback;
   NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface(
       &callback, NearbySharingService::ReceiveSurfaceState::kForeground);
@@ -2692,8 +2697,9 @@
 TEST_P(NearbySharingServiceImplTest,
        BackgroundReceiveSurfaceSelectedContactsVisibilityIsAdvertising) {
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
-  prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kSelectedContacts));
+  prefs_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kSelectedContacts));
   MockTransferUpdateCallback callback;
   NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface(
       &callback, NearbySharingService::ReceiveSurfaceState::kBackground);
@@ -2704,8 +2710,9 @@
 TEST_P(NearbySharingServiceImplTest,
        ForegroundReceiveSurfaceAllContactsVisibilityIsAdvertising) {
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
-  prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kAllContacts));
+  prefs_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kAllContacts));
   MockTransferUpdateCallback callback;
   NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface(
       &callback, NearbySharingService::ReceiveSurfaceState::kForeground);
@@ -2716,8 +2723,9 @@
 TEST_P(NearbySharingServiceImplTest,
        BackgroundReceiveSurfaceAllContactsVisibilityNotAdvertising) {
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
-  prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName,
-                    static_cast<int>(Visibility::kAllContacts));
+  prefs_.SetInteger(
+      prefs::kNearbySharingBackgroundVisibilityName,
+      static_cast<int>(nearby_share::mojom::Visibility::kAllContacts));
   MockTransferUpdateCallback callback;
   NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface(
       &callback, NearbySharingService::ReceiveSurfaceState::kBackground);
@@ -5154,11 +5162,12 @@
   // The existing scanner is destroyed when fast init notifications are turned
   // off.
   SetFastInitiationNotificationState(
-      FastInitiationNotificationState::kDisabledByUser);
+      nearby_share::mojom::FastInitiationNotificationState::kDisabledByUser);
   EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_created_count());
   EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_destroyed_count());
 
-  SetFastInitiationNotificationState(FastInitiationNotificationState::kEnabled);
+  SetFastInitiationNotificationState(
+      nearby_share::mojom::FastInitiationNotificationState::kEnabled);
   EXPECT_EQ(2u, fast_initiation_scanner_factory_->scanner_created_count());
   EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_destroyed_count());
 }
@@ -5240,7 +5249,7 @@
 TEST_P(NearbySharingServiceImplTest, VisibilityReminderTimerIsTriggered) {
   constexpr base::TimeDelta kTestDelay = base::Minutes(3);
   service_->set_visibility_reminder_timer_delay_for_testing(kTestDelay);
-  SetVisibility(Visibility::kAllContacts);
+  SetVisibility(nearby_share::mojom::Visibility::kAllContacts);
   task_environment_.FastForwardBy(kTestDelay);
   std::vector<message_center::Notification> notifications =
       notification_tester_->GetDisplayedNotificationsForType(
diff --git a/chrome/browser/nearby_sharing/payload_tracker.h b/chrome/browser/nearby_sharing/payload_tracker.h
index 623e940..7da93f1 100644
--- a/chrome/browser/nearby_sharing/payload_tracker.h
+++ b/chrome/browser/nearby_sharing/payload_tracker.h
@@ -11,9 +11,9 @@
 #include "base/functional/callback_forward.h"
 #include "base/time/time.h"
 #include "chrome/browser/nearby_sharing/attachment_info.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chrome/browser/nearby_sharing/share_target.h"
 #include "chrome/browser/nearby_sharing/transfer_metadata.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
 
 // Listens for incoming or outgoing transfer updates from Nearby Connections and
diff --git a/chrome/browser/nearby_sharing/public/cpp/BUILD.gn b/chrome/browser/nearby_sharing/public/cpp/BUILD.gn
index 0e79669..0b01812 100644
--- a/chrome/browser/nearby_sharing/public/cpp/BUILD.gn
+++ b/chrome/browser/nearby_sharing/public/cpp/BUILD.gn
@@ -8,8 +8,6 @@
   sources = [
     "nearby_connection.cc",
     "nearby_connection.h",
-    "nearby_connections_manager.cc",
-    "nearby_connections_manager.h",
   ]
 
   deps = [
@@ -22,18 +20,3 @@
 
   public_deps = [ "//third_party/nearby:presence_types" ]
 }
-
-static_library("test_support") {
-  testonly = true
-
-  sources = [
-    "fake_nearby_connections_manager.cc",
-    "fake_nearby_connections_manager.h",
-  ]
-
-  deps = [
-    ":cpp",
-    "//base",
-    "//chromeos/ash/services/nearby/public/mojom",
-  ]
-}
diff --git a/chrome/browser/nearby_sharing/share_target_info.h b/chrome/browser/nearby_sharing/share_target_info.h
index 306b1ec..221e943 100644
--- a/chrome/browser/nearby_sharing/share_target_info.h
+++ b/chrome/browser/nearby_sharing/share_target_info.h
@@ -15,8 +15,8 @@
 #include "chrome/browser/nearby_sharing/incoming_frames_reader.h"
 #include "chrome/browser/nearby_sharing/paired_key_verification_runner.h"
 #include "chrome/browser/nearby_sharing/payload_tracker.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chrome/browser/nearby_sharing/transfer_update_callback.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 
 class NearbyConnection;
 
diff --git a/chrome/browser/new_tab_page/bug-triage.md b/chrome/browser/new_tab_page/bug-triage.md
deleted file mode 100644
index dbe2331..0000000
--- a/chrome/browser/new_tab_page/bug-triage.md
+++ /dev/null
@@ -1,57 +0,0 @@
-Desktop New Tab Page Bug Triage Process
-=======================================
-
-last update: 04-19-2022
-
-The current triage process owner is `mahmadi@`.
-
-Instructions for Chrome Desktop NTP engineers participating in bug triage rotation.
-
-### Goals and Expectations:
-
-Every week, one engineer on the Desktop NTP team is responsible for triaging the
-newly reported **Unconfirmed**, **Untriaged**, and **Available** bugs filed
-against Desktop NTP. Every newly reported bug, should be preferably triaged
-within one week. This includes understanding the issue, recreating the issue if
-necessary, marking it as a duplicate if applicable, revising the status, setting
-priority, assigning the appropriate labels and component (incl. handing off to
-another component), and preferably assigning to an owner, if possible.
-
-Some bugs may already be **Assigned** to an owner but miss the appropriate
-labels. Those bugs are still considered untriaged and the responsible engineer
-should ensure the bug owner applies the appropriate labels, component, and
-priority.
-
-If a bug is deemed to have a high priority or is highly visible or disruptive to
-the user, the responsible engineer should inform the team (incl. the PM) so it
-can be prioritized accordingly.
-
-In addition to the newly reported bugs, the responsible engineer should also
-attempt to triage at least two bugs from [go/ntp-triage](http://goto/ntp-triage)
-and [go/ntp-triage-os](http://goto/ntp-triage) in that week. As of today these
-two lists have less than 100 bugs combined. If two of these bugs are triaged
-every week, there will be no untriaged NTP bugs by the end of the year!
-
-### Process:
-
-#### There are two ways to stay on top of the newly reported bugs:
-
-1. Set up an email alert for the following queries in
-[Monorail](https://bugs.chromium.org/p/chromium/issues/list). Or,
-   1. component:UI>Browser>NewTabPage OS=Windows,Mac,Linux,Chrome
-   2. component:UI>Browser>NewTabPage -has:OS (bugs that miss the OS label)
-2. Actively poll untriaged bugs at [go/ntp-triage](http://goto/ntp-triage) and
-[go/ntp-triage-os](http://goto/ntp-triage).
-
-#### For every bug being triaged:
-
-* Add either `ntp-backlog` (should be looked into) OR `ntp-icebox` (can be
-ignored for now) label.
-* If ntp-icebox set Pri to 3
-* If ntp-backlog set Pri to:
-  * 0 (needs to be fixed immediately)
-  * 1 (needs to be fixed by certain, usually next, milestone)
-  * 2 (nice to have for certain milestone, or affects unlaunched feature)
-* Add an appropriate `ntp-epic-*` label (see
-[go/ntp-epics](http://goto/ntp-epics) for currently used ones)
-* Make sure the correct OS labels are set.
diff --git a/chrome/browser/new_tab_page/modules/new_tab_page_modules.cc b/chrome/browser/new_tab_page/modules/new_tab_page_modules.cc
index ffecf31..6803e255 100644
--- a/chrome/browser/new_tab_page/modules/new_tab_page_modules.cc
+++ b/chrome/browser/new_tab_page/modules/new_tab_page_modules.cc
@@ -28,6 +28,11 @@
     bool drive_module_enabled) {
   std::vector<std::pair<const std::string, int>> details;
 
+  if (base::FeatureList::IsEnabled(ntp_features::kNtpCalendarModule)) {
+    details.emplace_back("google_calendar",
+                         IDS_NTP_MODULES_GOOGLE_CALENDAR_TITLE);
+  }
+
   if (drive_module_enabled) {
     details.emplace_back("drive", IDS_NTP_MODULES_DRIVE_SENTENCE);
   }
diff --git a/chrome/browser/pdf/pdf_extension_interactive_uitest.cc b/chrome/browser/pdf/pdf_extension_interactive_uitest.cc
index 7b323380..521d925 100644
--- a/chrome/browser/pdf/pdf_extension_interactive_uitest.cc
+++ b/chrome/browser/pdf/pdf_extension_interactive_uitest.cc
@@ -112,9 +112,10 @@
 
 }  // namespace
 
+// TODO(crbug.com/333802743): re-enable the test
 // For crbug.com/1038918
 IN_PROC_BROWSER_TEST_P(PDFExtensionInteractiveUITest,
-                       CtrlPageUpDownSwitchesTabs) {
+                       DISABLED_CtrlPageUpDownSwitchesTabs) {
   content::RenderFrameHost* extension_host = LoadPdfInNewTabGetExtensionHost(
       embedded_test_server()->GetURL("/pdf/test.pdf"));
   ASSERT_TRUE(extension_host);
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
index 1ddb8c2..8ab3ce9 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -788,6 +788,13 @@
             "org.chromium.chrome.browser.tabmodel.TabPersistentStore."
                     + "HAS_RUN_MULTI_INSTANCE_FILE_MIGRATION";
 
+    public static final String TAB_DECLUTTER_ARCHIVE_ENABLED = "Chrome.Tab.ArchiveEnabled";
+    public static final String TAB_DECLUTTER_ARCHIVE_TIME_DELTA_HOURS =
+            "Chrome.Tab.ArchiveTimeDeltaHours";
+    public static final String TAB_DECLUTTER_AUTO_DELETE_ENABLED =
+            "Chrome.Tab.ArchiveAutoDeleteEnabled";
+    public static final String TAB_DECLUTTER_AUTO_DELETE_TIME_DELTA_HOURS =
+            "Chrome.Tab.ArchiveAutoDeleteTimeDeltaHours";
     public static final String TAB_ID_MANAGER_NEXT_ID =
             "org.chromium.chrome.browser.tab.TabIdManager.NEXT_ID";
 
@@ -1010,6 +1017,10 @@
                 SWAA_TIMESTAMP,
                 SWAA_STATUS,
                 TABBED_ACTIVITY_LAST_VISIBLE_TIME_MS,
+                TAB_DECLUTTER_ARCHIVE_ENABLED,
+                TAB_DECLUTTER_ARCHIVE_TIME_DELTA_HOURS,
+                TAB_DECLUTTER_AUTO_DELETE_ENABLED,
+                TAB_DECLUTTER_AUTO_DELETE_TIME_DELTA_HOURS,
                 TWA_DISCLOSURE_SEEN_PACKAGES,
                 UMA_ON_POSTCREATE_COUNTER,
                 UMA_ON_RESUME_COUNTER,
diff --git a/chrome/browser/privacy_sandbox/browsing_topics_settings_interactive_uitest.cc b/chrome/browser/privacy_sandbox/browsing_topics_settings_interactive_uitest.cc
index 6d14fb3..a499f93a 100644
--- a/chrome/browser/privacy_sandbox/browsing_topics_settings_interactive_uitest.cc
+++ b/chrome/browser/privacy_sandbox/browsing_topics_settings_interactive_uitest.cc
@@ -22,6 +22,7 @@
 using DeepQuery = WebContentsInteractionTestUtil::DeepQuery;
 
 DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kPrivacySandboxTopicsElementId);
+DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kIronCollapseFinishedTransitioningEvent);
 
 constexpr char BlockedTopicsListLengthFunc[] =
     "(el) => el.querySelectorAll('privacy-sandbox-interest-item').length";
@@ -55,13 +56,14 @@
     browser()->profile()->GetPrefs()->SetBoolean(
         prefs::kPrivacySandboxM1TopicsEnabled, true);
     InteractiveBrowserTest::SetUpOnMainThread();
+    browser()->window()->SetBounds(gfx::Rect(600, 700));
   }
 
-  void BlockFirstTopic() {
+  void BlockTopic(int topic_id) {
     auto* const privacy_sandbox_service =
         PrivacySandboxServiceFactory::GetForProfile(browser()->profile());
     privacy_sandbox_service->SetTopicAllowed(
-        privacy_sandbox::CanonicalTopic(browsing_topics::Topic(1),
+        privacy_sandbox::CanonicalTopic(browsing_topics::Topic(topic_id),
                                         /*taxonomy_version=*/2),
         false);
   }
@@ -80,28 +82,37 @@
   }
 
   const DeepQuery firstToggle = GetManageTopicsPageQuery() + "#toggle-1";
+  const DeepQuery secondToggle = GetManageTopicsPageQuery() + "#toggle-57";
   const DeepQuery blockedTopicsList =
       GetAdTopicsPageQuery() + "#blockedTopicsList";
   const DeepQuery firstBlockedItemButton =
       (blockedTopicsList + "privacy-sandbox-interest-item") + "cr-button";
+  const DeepQuery blockedTopicsRow =
+      GetAdTopicsPageQuery() + "#blockedTopicsRow";
+  const DeepQuery ironCollapse = GetAdTopicsPageQuery() + "iron-collapse";
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-// Block topic(1) through PS service and validate that it's toggled OFF (checked
-// == false) on the Manage Topics Page.
+// Block topic(1) and topic(57) through PS service and validate that it's
+// toggled OFF (checked == false) on the Manage Topics Page. Validate
+// screenshot.
 IN_PROC_BROWSER_TEST_F(PrivacySandboxSettingsTopicsInteractiveTest,
-                       StartWithOneBlockedTopic) {
-  BlockFirstTopic();
+                       StartWithTwoBlockedTopics) {
+  BlockTopic(1);
+  BlockTopic(57);
   RunTestSequence(
       InstrumentTab(kPrivacySandboxTopicsElementId),
       NavigateWebContents(kPrivacySandboxTopicsElementId,
                           GURL(chrome::kPrivacySandboxManageTopicsURL)),
       CheckJsResultAt(kPrivacySandboxTopicsElementId, firstToggle,
                       "(el) => el.checked", false),
-      NavigateWebContents(kPrivacySandboxTopicsElementId,
-                          GURL(chrome::kPrivacySandboxAdTopicsURL)));
+      CheckJsResultAt(kPrivacySandboxTopicsElementId, secondToggle,
+                      "(el) => el.checked", false),
+      SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue,
+                              "Screenshot not supported in all test modes."),
+      Screenshot(ContentsWebView::kContentsWebViewElementId, "", "5405426"));
 }
 
 // Block first topic on Manage Topics Page. Validate that it is toggled off
@@ -138,7 +149,7 @@
 // topic toggle is ON (checked == true).
 IN_PROC_BROWSER_TEST_F(PrivacySandboxSettingsTopicsInteractiveTest,
                        UnblockOneTopicOnAdTopicsPage) {
-  BlockFirstTopic();
+  BlockTopic(1);
   RunTestSequence(
       InstrumentTab(kPrivacySandboxTopicsElementId),
       NavigateWebContents(kPrivacySandboxTopicsElementId,
@@ -179,4 +190,55 @@
         )",
                       false));
 }
+
+// Pixel test for Manage Topics Page with all topics set to their default state.
+IN_PROC_BROWSER_TEST_F(PrivacySandboxSettingsTopicsInteractiveTest,
+                       ValidateScreenshotDefaultManageTopicsPage) {
+  RunTestSequence(
+      InstrumentTab(kPrivacySandboxTopicsElementId),
+      NavigateWebContents(kPrivacySandboxTopicsElementId,
+                          GURL(chrome::kPrivacySandboxManageTopicsURL)),
+      SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue,
+                              "Screenshot not supported in all test modes."),
+      Screenshot(ContentsWebView::kContentsWebViewElementId, "", "5405426"));
+}
+
+// Pixel test for Manage Topics Page and Ad Topics Page with blocking the first
+// topic through JS commands.
+IN_PROC_BROWSER_TEST_F(PrivacySandboxSettingsTopicsInteractiveTest,
+                       ValidateScreenshotsWithFirstTopicBlockedWithJS) {
+  StateChange ironCollapseFinishedTransitioning;
+  ironCollapseFinishedTransitioning.event =
+      kIronCollapseFinishedTransitioningEvent;
+  ironCollapseFinishedTransitioning.type =
+      StateChange::Type::kExistsAndConditionTrue;
+  ironCollapseFinishedTransitioning.where = ironCollapse;
+  ironCollapseFinishedTransitioning.test_function =
+      "(el) => { return el.transitioning === false }";
+
+  RunTestSequence(
+      InstrumentTab(kPrivacySandboxTopicsElementId),
+      NavigateWebContents(kPrivacySandboxTopicsElementId,
+                          GURL(chrome::kPrivacySandboxManageTopicsURL)),
+      ExecuteJsAt(kPrivacySandboxTopicsElementId, firstToggle,
+                  "(el) => { el.click(); }"),
+      SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue,
+                              "Screenshot not supported in all test modes."),
+      Screenshot(ContentsWebView::kContentsWebViewElementId,
+                 "ManageTopicsPageAfterFirstTopicBlocked", "5405426"),
+      NavigateWebContents(kPrivacySandboxTopicsElementId,
+                          GURL(chrome::kPrivacySandboxAdTopicsURL)),
+      ExecuteJsAt(kPrivacySandboxTopicsElementId, ironCollapse,
+                  "(el) => { el.noAnimation = true; }"),
+      ExecuteJsAt(kPrivacySandboxTopicsElementId, blockedTopicsRow,
+                  "(el) => { el.click(); }"),
+      WaitForStateChange(kPrivacySandboxTopicsElementId,
+                         ironCollapseFinishedTransitioning),
+      Screenshot(ContentsWebView::kContentsWebViewElementId,
+                 "AdTopicsPageAfterFirstTopicBlocked", "5405426"),
+      CheckResult([this]() { return GetBlockedTopicsSize(); }, 1u,
+                  "Checking that there is only one blocked topic"),
+      CheckResult([this]() { return GetBlockedTopicsFirstTopicId(); }, 1,
+                  "Checking that the one blocked topic is topic(1)"));
+}
 }  // namespace
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
index 974095f..c91f718 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
@@ -302,7 +302,7 @@
             }
 
             WebContents webContents = tab.getWebContents();
-            if (webContents != null) {
+            if (webContents != null && mHandle != 0L) {
                 TranslateBridge.removeTranslationObserver(webContents, mHandle);
             }
 
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/PlayerMediator.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/PlayerMediator.java
index a12074d..45bda462 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/PlayerMediator.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/PlayerMediator.java
@@ -92,7 +92,7 @@
 
                 @Override
                 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                    if (!fromUser) {
+                    if (!fromUser || mPlayback == null) {
                         return;
                     }
                     float percent = (float) progress / (float) seekBar.getMax();
@@ -294,6 +294,10 @@
 
     @Override
     public void onSpeedChange(float newSpeed) {
+        if (mPlayback == null) {
+            return;
+        }
+
         ReadAloudPrefs.setSpeed(mDelegate.getPrefService(), newSpeed);
         mPlayback.setRate(newSpeed);
         if (newSpeed >= 2.0f) {
diff --git a/chrome/browser/resources/app_home/app_home_empty_page.html b/chrome/browser/resources/app_home/app_home_empty_page.html
index 624f2fe..23feae9 100644
--- a/chrome/browser/resources/app_home/app_home_empty_page.html
+++ b/chrome/browser/resources/app_home/app_home_empty_page.html
@@ -15,28 +15,16 @@
     margin-top: 0;
 }
 
+/* Force dark mode colors. */
 cr-button {
-    /* Used to force dark mode */
-    --active-bg: black linear-gradient(rgba(255, 255, 255, .06),
-                                             rgba(255, 255, 255, .06));
-    --active-shadow-rgb: 0, 0, 0;
-    --active-shadow-action-rgb: var(--google-blue-500-rgb);
-    --bg-action: var(--google-blue-300);
-    --border-color: var(--google-grey-700);
-    --disabled-bg-action: var(--google-grey-800);
-    --disabled-bg: transparent;
-    --disabled-border-color: var(--google-grey-800);
-    --disabled-text-color: var(--google-grey-500);
-    --focus-shadow-color: rgba(var(--google-blue-300-rgb), .5);
-    --hover-bg-action: var(--bg-action)
-        linear-gradient(rgba(0, 0, 0, .08), rgba(0, 0, 0, .08));
-    --hover-bg-color: rgba(var(--google-blue-300-rgb), .08);
-    --ink-color-action: black;
-    --ink-color: var(--google-blue-300);
-    --ripple-opacity-action: .16;
-    --ripple-opacity: .16;
-    --text-color-action: var(--google-grey-900);
-    --text-color: var(--google-blue-300);
+  --cr-button-border-color: var(--google-grey-700);
+  --cr-button-text-color: var(--google-blue-300);
+  --cr-button-ripple-color: var(--google-blue-300);
+  --cr-button-ripple-opacity: .16;
+}
+
+cr-button:hover {
+  --cr-button-background-color: rgba(var(--google-blue-300-rgb), .08);
 }
 </style>
 
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/facegaze/facegaze_test_support.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/facegaze/facegaze_test_support.js
index 96ed581..ae813fc2 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/facegaze/facegaze_test_support.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/facegaze/facegaze_test_support.js
@@ -65,7 +65,7 @@
    * @param {number} x
    * @param {number} y
    */
-  async waitForMouseLocation(x, y) {
+  async waitForCursorLocation(x, y) {
     const desktop = await new Promise(resolve => {
       chrome.automation.getDesktop(d => resolve(d));
     });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index 2c73894..5ec03d9 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -63,8 +63,10 @@
   "background/injected_script_loader.ts",
   "background/output/output_interface.ts",
   "chromevox_loader.ts",
+  "common/braille/braille_command_data.ts",
   "common/braille/braille_key_types.ts",
   "common/braille/braille_table.ts",
+  "common/earcon_id.ts",
   "common/key_util.ts",
   "common/msgs.ts",
   "common/tts_types.ts",
@@ -145,7 +147,6 @@
   "background/tts_interface.js",
   "background/forced_action_path.js",
   "common/background_bridge.js",
-  "common/braille/braille_command_data.js",
   "common/braille/nav_braille.js",
   "common/bridge_callback_manager.js",
   "common/bridge_constants.js",
@@ -153,7 +154,6 @@
   "common/command.js",
   "common/command_store.js",
   "common/custom_automation_event.js",
-  "common/earcon_id.js",
   "common/event_source_type.js",
   "common/gesture_command_data.js",
   "common/key_map.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcon_engine.ts b/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcon_engine.ts
index 0e63da8..f0a13856 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcon_engine.ts
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcon_engine.ts
@@ -8,7 +8,7 @@
  * rest of the code.
  */
 
-import { EarconId } from '../common/earcon_id.js';
+import {EarconId} from '../common/earcon_id.js';
 
 interface PlayProperties {
   pitch?: number;
@@ -139,7 +139,7 @@
    * Maps a earcon name to the last source input audio for that
    * earcon.
    */
-  private lastEarconSources_: { [id: EarconId]: (AudioNode | undefined) } = {};
+  private lastEarconSources_: Partial<Record<EarconId, AudioNode>> = {};
 
   private currentTrackedEarcon_?: EarconId;
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/braille/braille_command_data.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/braille/braille_command_data.js
deleted file mode 100644
index 9411e71..0000000
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/braille/braille_command_data.js
+++ /dev/null
@@ -1,162 +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.
-
-/**
- * @fileoverview ChromeVox braille command data.
- */
-import {TestImportManager} from '/common/testing/test_import_manager.js';
-
-import {Command} from '../command.js';
-import {Msgs} from '../msgs.js';
-
-export const BrailleCommandData = {};
-
-/**
- * Maps a dot pattern to a command.
- * @type {!Object<number, !Command>}
- */
-BrailleCommandData.DOT_PATTERN_TO_COMMAND = {};
-
-/**
- * Makes a dot pattern given a list of dots numbered from 1 to 8 arranged in a
- * braille cell (a 2 x 4 dot grid).
- * @param {Array<number>} dots The dots to be set in the returned pattern.
- * @return {number}
- */
-BrailleCommandData.makeDotPattern = function(dots) {
-  return dots.reduce((pattern, cell) => pattern | (1 << cell - 1), 0);
-};
-
-/**
- * Gets a braille command based on a dot pattern from a chord.
- * @param {number} dots
- * @return {?Command}
- */
-BrailleCommandData.getCommand = function(dots) {
-  return BrailleCommandData.DOT_PATTERN_TO_COMMAND[dots];
-};
-
-/**
- * Gets a dot shortcut for a command.
- * @param {!Command} command
- * @param {boolean=} opt_chord True if the pattern comes from a chord.
- * @return {string} The shortcut.
- */
-BrailleCommandData.getDotShortcut = function(command, opt_chord) {
-  const commandDots = BrailleCommandData.getDots(command);
-  return BrailleCommandData.makeShortcutText(commandDots, opt_chord);
-};
-
-/**
- * @param {number} pattern
- * @param {boolean=} opt_chord
- * @return {string}
- */
-BrailleCommandData.makeShortcutText = function(pattern, opt_chord) {
-  const dots = [];
-  for (let shifter = 0; shifter <= 7; shifter++) {
-    if ((1 << shifter) & pattern) {
-      dots.push(shifter + 1);
-    }
-  }
-  let msgid;
-  if (dots.length > 1) {
-    msgid = 'braille_dots';
-  } else if (dots.length === 1) {
-    msgid = 'braille_dot';
-  }
-
-  if (msgid) {
-    let dotText = Msgs.getMsg(msgid, [dots.join('-')]);
-    if (opt_chord) {
-      dotText = Msgs.getMsg('braille_chord', [dotText]);
-    }
-    return dotText;
-  }
-  return '';
-};
-
-/**
- * @param {!Command} command
- * @return {number} The dot pattern for |command|.
- */
-BrailleCommandData.getDots = function(command) {
-  for (let key in BrailleCommandData.DOT_PATTERN_TO_COMMAND) {
-    key = parseInt(key, 10);
-    if (command === BrailleCommandData.DOT_PATTERN_TO_COMMAND[key]) {
-      return key;
-    }
-  }
-  return 0;
-};
-
-/**
- * @private
- */
-BrailleCommandData.init_ = function() {
-  const map = function(dots, command) {
-    const pattern = BrailleCommandData.makeDotPattern(dots);
-    const existingCommand = BrailleCommandData.DOT_PATTERN_TO_COMMAND[pattern];
-    if (existingCommand) {
-      throw 'Braille command pattern already exists: ' + dots + ' ' +
-          existingCommand + '. Trying to map ' + command;
-    }
-
-    BrailleCommandData.DOT_PATTERN_TO_COMMAND[pattern] = command;
-  };
-
-  map([2, 3], Command.PREVIOUS_GROUP);
-  map([5, 6], Command.NEXT_GROUP);
-  map([1], Command.PREVIOUS_OBJECT);
-  map([4], Command.NEXT_OBJECT);
-  map([2], Command.PREVIOUS_WORD);
-  map([5], Command.NEXT_WORD);
-  map([3], Command.PREVIOUS_CHARACTER);
-  map([6], Command.NEXT_CHARACTER);
-  map([1, 2, 3], Command.JUMP_TO_TOP);
-  map([4, 5, 6], Command.JUMP_TO_BOTTOM);
-
-  map([1, 4], Command.FULLY_DESCRIBE);
-  map([1, 3, 4], Command.CONTEXT_MENU);
-  map([1, 2, 3, 5], Command.READ_FROM_HERE);
-  map([2, 3, 4], Command.TOGGLE_SELECTION);
-
-  // Forward jump.
-  map([1, 2], Command.NEXT_BUTTON);
-  map([1, 5], Command.NEXT_EDIT_TEXT);
-  map([1, 2, 4], Command.NEXT_FORM_FIELD);
-  map([1, 2, 5], Command.NEXT_HEADING);
-  map([4, 5], Command.NEXT_LINK);
-  map([2, 3, 4, 5], Command.NEXT_TABLE);
-
-  // Backward jump.
-  map([1, 2, 7], Command.PREVIOUS_BUTTON);
-  map([1, 5, 7], Command.PREVIOUS_EDIT_TEXT);
-  map([1, 2, 4, 7], Command.PREVIOUS_FORM_FIELD);
-  map([1, 2, 5, 7], Command.PREVIOUS_HEADING);
-  map([4, 5, 7], Command.PREVIOUS_LINK);
-  map([2, 3, 4, 5, 7], Command.PREVIOUS_TABLE);
-
-  map([8], Command.FORCE_CLICK_ON_CURRENT_ITEM);
-  map([3, 4], Command.TOGGLE_SEARCH_WIDGET);
-
-  // Question.
-  map([1, 4, 5, 6], Command.TOGGLE_KEYBOARD_HELP);
-
-  // All cells.
-  map([1, 2, 3, 4, 5, 6], Command.TOGGLE_SCREEN);
-
-  // s.
-  map([1, 2, 3, 4, 5], Command.TOGGLE_SPEECH_ON_OR_OFF);
-
-  // g.
-  map([1, 2, 4, 5], Command.TOGGLE_BRAILLE_TABLE);
-
-  // Stop speech.
-  map([5, 6, 7], Command.STOP_SPEECH);
-};
-
-BrailleCommandData.init_();
-
-TestImportManager.exportForTesting(['BrailleCommandData', BrailleCommandData]);
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/braille/braille_command_data.ts b/chrome/browser/resources/chromeos/accessibility/chromevox/common/braille/braille_command_data.ts
new file mode 100644
index 0000000..2f0f4bc6
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/braille/braille_command_data.ts
@@ -0,0 +1,145 @@
+// 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.
+
+/**
+ * @fileoverview ChromeVox braille command data.
+ */
+import {TestImportManager} from '/common/testing/test_import_manager.js';
+
+import {Command} from '../command.js';
+import {Msgs} from '../msgs.js';
+
+export namespace BrailleCommandData {
+  /** Maps a dot pattern to a command. */
+  export const DOT_PATTERN_TO_COMMAND: Record<number, Command> = {};
+
+  /**
+   * Makes a dot pattern given a list of dots numbered from 1 to 8 arranged in a
+   * braille cell (a 2 x 4 dot grid).
+   * @param dots The dots to be set in the returned pattern.
+   */
+  export function makeDotPattern(dots: number[]): number {
+    return dots.reduce(
+        (pattern: number, cell: number) => pattern | (1 << cell - 1), 0);
+  }
+
+  /** Gets a braille command based on a dot pattern from a chord. */
+  export function getCommand(dots: number): Command|undefined {
+    return DOT_PATTERN_TO_COMMAND[dots];
+  }
+
+  /**
+   * Gets a dot shortcut for a command.
+   * @param chord True if the pattern comes from a chord.
+   * @return The shortcut.
+   */
+  export function getDotShortcut(command: Command, chord?: boolean): string {
+    const commandDots = getDots(command);
+    return makeShortcutText(commandDots, chord);
+  }
+
+  export function makeShortcutText(pattern: number, chord?: boolean): string {
+    const dots: number[] = [];
+    for (let shifter = 0; shifter <= 7; shifter++) {
+      if ((1 << shifter) & pattern) {
+        dots.push(shifter + 1);
+      }
+    }
+    let msgid;
+    if (dots.length > 1) {
+      msgid = 'braille_dots';
+    } else if (dots.length === 1) {
+      msgid = 'braille_dot';
+    }
+
+    if (msgid) {
+      let dotText = Msgs.getMsg(msgid, [dots.join('-')]);
+      if (chord) {
+        dotText = Msgs.getMsg('braille_chord', [dotText]);
+      }
+      return dotText;
+    }
+    return '';
+  }
+
+  /**
+   * @return The dot pattern for |command|.
+   */
+  export function getDots(command: Command): number {
+    for (const keyStr in DOT_PATTERN_TO_COMMAND) {
+      const key = parseInt(keyStr, 10);
+      if (command === DOT_PATTERN_TO_COMMAND[key]) {
+        return key;
+      }
+    }
+    return 0;
+  }
+
+  export function init(): void {
+    const map = function(dots: number[], command: Command): void {
+      const pattern = makeDotPattern(dots);
+      const existingCommand = DOT_PATTERN_TO_COMMAND[pattern];
+      if (existingCommand) {
+        throw 'Braille command pattern already exists: ' + dots + ' ' +
+            existingCommand + '. Trying to map ' + command;
+      }
+
+      DOT_PATTERN_TO_COMMAND[pattern] = command;
+    };
+
+    map([2, 3], Command.PREVIOUS_GROUP);
+    map([5, 6], Command.NEXT_GROUP);
+    map([1], Command.PREVIOUS_OBJECT);
+    map([4], Command.NEXT_OBJECT);
+    map([2], Command.PREVIOUS_WORD);
+    map([5], Command.NEXT_WORD);
+    map([3], Command.PREVIOUS_CHARACTER);
+    map([6], Command.NEXT_CHARACTER);
+    map([1, 2, 3], Command.JUMP_TO_TOP);
+    map([4, 5, 6], Command.JUMP_TO_BOTTOM);
+
+    map([1, 4], Command.FULLY_DESCRIBE);
+    map([1, 3, 4], Command.CONTEXT_MENU);
+    map([1, 2, 3, 5], Command.READ_FROM_HERE);
+    map([2, 3, 4], Command.TOGGLE_SELECTION);
+
+    // Forward jump.
+    map([1, 2], Command.NEXT_BUTTON);
+    map([1, 5], Command.NEXT_EDIT_TEXT);
+    map([1, 2, 4], Command.NEXT_FORM_FIELD);
+    map([1, 2, 5], Command.NEXT_HEADING);
+    map([4, 5], Command.NEXT_LINK);
+    map([2, 3, 4, 5], Command.NEXT_TABLE);
+
+    // Backward jump.
+    map([1, 2, 7], Command.PREVIOUS_BUTTON);
+    map([1, 5, 7], Command.PREVIOUS_EDIT_TEXT);
+    map([1, 2, 4, 7], Command.PREVIOUS_FORM_FIELD);
+    map([1, 2, 5, 7], Command.PREVIOUS_HEADING);
+    map([4, 5, 7], Command.PREVIOUS_LINK);
+    map([2, 3, 4, 5, 7], Command.PREVIOUS_TABLE);
+
+    map([8], Command.FORCE_CLICK_ON_CURRENT_ITEM);
+    map([3, 4], Command.TOGGLE_SEARCH_WIDGET);
+
+    // Question.
+    map([1, 4, 5, 6], Command.TOGGLE_KEYBOARD_HELP);
+
+    // All cells.
+    map([1, 2, 3, 4, 5, 6], Command.TOGGLE_SCREEN);
+
+    // s.
+    map([1, 2, 3, 4, 5], Command.TOGGLE_SPEECH_ON_OR_OFF);
+
+    // g.
+    map([1, 2, 4, 5], Command.TOGGLE_BRAILLE_TABLE);
+
+    // Stop speech.
+    map([5, 6, 7], Command.STOP_SPEECH);
+  }
+
+  init();
+}
+
+TestImportManager.exportForTesting(['BrailleCommandData', BrailleCommandData]);
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/braille/braille_command_data_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/braille/braille_command_data_test.js
index 60d319a..e49bc3c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/braille/braille_command_data_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/braille/braille_command_data_test.js
@@ -14,7 +14,7 @@
 AX_TEST_F('ChromeVoxBrailleCommandDataTest', 'Duplicates', function() {
   try {
     BrailleCommandData.DOT_PATTERN_TO_COMMAND = [];
-    BrailleCommandData.init_();
+    BrailleCommandData.init();
   } catch (e) {
     assertNotReached(e.toString());
   }
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/earcon_id.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/earcon_id.js
deleted file mode 100644
index 6a6fd777..0000000
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/earcon_id.js
+++ /dev/null
@@ -1,75 +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.
-
-/**
- * @fileoverview Data relating to the earcons used in ChromeVox.
- */
-import {TestImportManager} from '/common/testing/test_import_manager.js';
-
-/**
- * Earcon names.
- * @enum {string}
- */
-export const EarconId = {
-  ALERT_MODAL: 'alert_modal',
-  ALERT_NONMODAL: 'alert_nonmodal',
-  BUTTON: 'button',
-  CHECK_OFF: 'check_off',
-  CHECK_ON: 'check_on',
-  CHROMEVOX_LOADING: 'chromevox_loading',
-  CHROMEVOX_LOADED: 'chromevox_loaded',
-  EDITABLE_TEXT: 'editable_text',
-  INVALID_KEYPRESS: 'invalid_keypress',
-  LINK: 'link',
-  LISTBOX: 'listbox',
-  LIST_ITEM: 'list_item',
-  LONG_DESC: 'long_desc',
-  MATH: 'math',
-  NO_POINTER_ANCHOR: 'no_pointer_anchor',
-  OBJECT_CLOSE: 'object_close',
-  OBJECT_ENTER: 'object_enter',
-  OBJECT_EXIT: 'object_exit',
-  OBJECT_OPEN: 'object_open',
-  OBJECT_SELECT: 'object_select',
-  PAGE_FINISH_LOADING: 'page_finish_loading',
-  PAGE_START_LOADING: 'page_start_loading',
-  POP_UP_BUTTON: 'pop_up_button',
-  RECOVER_FOCUS: 'recover_focus',
-  SELECTION: 'selection',
-  SELECTION_REVERSE: 'selection_reverse',
-  SKIP: 'skip',
-  SLIDER: 'slider',
-  SMART_STICKY_MODE_OFF: 'smart_sticky_mode_off',
-  SMART_STICKY_MODE_ON: 'smart_sticky_mode_on',
-  WRAP: 'wrap',
-  WRAP_EDGE: 'wrap_edge',
-};
-
-/**
- * Maps a earcon id to a message id description.
- * Only add mappings for earcons used in ChromeVox Next. This map gets
- * used to generate tutorial content.
- * @type {Object<string, string>}
- */
-export const EarconDescription = {
-  [EarconId.ALERT_MODAL]: 'alert_modal_earcon_description',
-  [EarconId.ALERT_NONMODAL]: 'alert_nonmodal_earcon_description',
-  [EarconId.BUTTON]: 'button_earcon_description',
-  [EarconId.CHECK_OFF]: 'check_off_earcon_description',
-  [EarconId.CHECK_ON]: 'check_on_earcon_description',
-  [EarconId.CHROMEVOX_LOADING]: 'chromevox_loading_earcon_description',
-  [EarconId.EDITABLE_TEXT]: 'editable_text_earcon_description',
-  [EarconId.INVALID_KEYPRESS]: 'invalid_keypress_earcon_description',
-  [EarconId.LINK]: 'link_earcon_description',
-  [EarconId.LISTBOX]: 'listbox_earcon_description',
-  [EarconId.NO_POINTER_ANCHOR]: 'no_pointer_anchor_earcon_description',
-  [EarconId.PAGE_START_LOADING]: 'page_start_loading_earcon_description',
-  [EarconId.POP_UP_BUTTON]: 'pop_up_button_earcon_description',
-  [EarconId.SLIDER]: 'slider_earcon_description',
-  [EarconId.SMART_STICKY_MODE_OFF]: 'smart_sticky_mode_off_earcon_description',
-  [EarconId.SMART_STICKY_MODE_ON]: 'smart_sticky_mode_on_earcon_description',
-  [EarconId.WRAP]: 'wrap_earcon_description',
-};
-
-TestImportManager.exportForTesting(['EarconId', EarconId]);
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/earcon_id.ts b/chrome/browser/resources/chromeos/accessibility/chromevox/common/earcon_id.ts
new file mode 100644
index 0000000..a9d3554
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/earcon_id.ts
@@ -0,0 +1,71 @@
+// 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.
+
+/**
+ * @fileoverview Data relating to the earcons used in ChromeVox.
+ */
+import {TestImportManager} from '/common/testing/test_import_manager.js';
+
+/** Earcon names. */
+export enum EarconId {
+  ALERT_MODAL = 'alert_modal',
+  ALERT_NONMODAL = 'alert_nonmodal',
+  BUTTON = 'button',
+  CHECK_OFF = 'check_off',
+  CHECK_ON = 'check_on',
+  CHROMEVOX_LOADING = 'chromevox_loading',
+  CHROMEVOX_LOADED = 'chromevox_loaded',
+  EDITABLE_TEXT = 'editable_text',
+  INVALID_KEYPRESS = 'invalid_keypress',
+  LINK = 'link',
+  LISTBOX = 'listbox',
+  LIST_ITEM = 'list_item',
+  LONG_DESC = 'long_desc',
+  MATH = 'math',
+  NO_POINTER_ANCHOR = 'no_pointer_anchor',
+  OBJECT_CLOSE = 'object_close',
+  OBJECT_ENTER = 'object_enter',
+  OBJECT_EXIT = 'object_exit',
+  OBJECT_OPEN = 'object_open',
+  OBJECT_SELECT = 'object_select',
+  PAGE_FINISH_LOADING = 'page_finish_loading',
+  PAGE_START_LOADING = 'page_start_loading',
+  POP_UP_BUTTON = 'pop_up_button',
+  RECOVER_FOCUS = 'recover_focus',
+  SELECTION = 'selection',
+  SELECTION_REVERSE = 'selection_reverse',
+  SKIP = 'skip',
+  SLIDER = 'slider',
+  SMART_STICKY_MODE_OFF = 'smart_sticky_mode_off',
+  SMART_STICKY_MODE_ON = 'smart_sticky_mode_on',
+  WRAP = 'wrap',
+  WRAP_EDGE = 'wrap_edge',
+}
+
+/**
+ * Maps a earcon id to a message id description.
+ * Only add mappings for earcons used in ChromeVox Next. This map gets
+ * used to generate tutorial content.
+ */
+export const EarconDescription: Partial<Record<EarconId, string>> = {
+  [EarconId.ALERT_MODAL]: 'alert_modal_earcon_description',
+  [EarconId.ALERT_NONMODAL]: 'alert_nonmodal_earcon_description',
+  [EarconId.BUTTON]: 'button_earcon_description',
+  [EarconId.CHECK_OFF]: 'check_off_earcon_description',
+  [EarconId.CHECK_ON]: 'check_on_earcon_description',
+  [EarconId.CHROMEVOX_LOADING]: 'chromevox_loading_earcon_description',
+  [EarconId.EDITABLE_TEXT]: 'editable_text_earcon_description',
+  [EarconId.INVALID_KEYPRESS]: 'invalid_keypress_earcon_description',
+  [EarconId.LINK]: 'link_earcon_description',
+  [EarconId.LISTBOX]: 'listbox_earcon_description',
+  [EarconId.NO_POINTER_ANCHOR]: 'no_pointer_anchor_earcon_description',
+  [EarconId.PAGE_START_LOADING]: 'page_start_loading_earcon_description',
+  [EarconId.POP_UP_BUTTON]: 'pop_up_button_earcon_description',
+  [EarconId.SLIDER]: 'slider_earcon_description',
+  [EarconId.SMART_STICKY_MODE_OFF]: 'smart_sticky_mode_off_earcon_description',
+  [EarconId.SMART_STICKY_MODE_ON]: 'smart_sticky_mode_on_earcon_description',
+  [EarconId.WRAP]: 'wrap_earcon_description',
+};
+
+TestImportManager.exportForTesting(['EarconId', EarconId]);
diff --git a/chrome/browser/resources/chromeos/accessibility/common/tutorial/chromevox_tutorial.js b/chrome/browser/resources/chromeos/accessibility/common/tutorial/chromevox_tutorial.js
index a70443ebb..52b0d1f 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/tutorial/chromevox_tutorial.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/tutorial/chromevox_tutorial.js
@@ -18,7 +18,7 @@
 import {LessonMenu} from './lesson_menu.js';
 import {Localization} from './localization.js';
 import {MainMenu} from './main_menu.js';
-import {NavigationButtons} from './navigation_buttons.js';
+import './navigation_buttons.js';
 import {TutorialBehavior} from './tutorial_behavior.js';
 import {TutorialLesson} from './tutorial_lesson.js';
 
diff --git a/chrome/browser/resources/chromeos/accessibility/common/tutorial/lesson_container.js b/chrome/browser/resources/chromeos/accessibility/common/tutorial/lesson_container.js
index ec0ad1b..cbb6e691 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/tutorial/lesson_container.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/tutorial/lesson_container.js
@@ -7,39 +7,54 @@
  * the tutorial.
  */
 
-import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {LessonData, Screen} from './constants.js';
 import {TutorialLesson} from './tutorial_lesson.js';
 
-export const LessonContainer = Polymer({
-  is: 'lesson-container',
+/** @polymer */
+export class LessonContainer extends PolymerElement {
+  static get is() {
+    return 'lesson-container';
+  }
 
-  _template: html`{__html_template__}`,
+  static get template() {
+    return html`{__html_template__}`;
+  }
 
-  properties: {
-    /** @type {Array<!LessonData>} */
-    lessonData: {type: Array},
+  static get properties() {
+    return {
+      /** @type {Array<!LessonData>} */
+      lessonData: {type: Array},
 
-    // Observed properties.
-    /** @type {Screen} */
-    activeScreen: {type: String},
+      // Observed properties.
+      /** @type {Screen} */
+      activeScreen: {type: String},
 
-    /** @type {number} */
-    activeLessonId: {type: Number},
-  },
+      /** @type {number} */
+      activeLessonId: {type: Number},
+    };
+  }
 
   /** @override */
   ready() {
+    super.ready();
+
     this.$.lessonTemplate.addEventListener('dom-change', evt => {
       // Executes once all lessons have been added to the dom.
       this.onLessonsLoaded_();
     });
-  },
+  }
+
+  /** @param {string} eventName  */
+  fire(eventName) {
+    this.dispatchEvent(
+        new CustomEvent(eventName, {bubbles: true, composed: true}));
+  }
 
   /** @private */
   onLessonsLoaded_() {
     this.fire('lessons-loaded');
-  },
+  }
 
   /**
    * @param {Screen} activeScreen
@@ -48,7 +63,7 @@
    */
   shouldHideLessonContainer_(activeScreen) {
     return activeScreen !== Screen.LESSON;
-  },
+  }
 
   /** @return {!Array<!TutorialLesson>} */
   getLessonsFromDom() {
@@ -64,5 +79,6 @@
     }
 
     return lessons;
-  },
-});
+  }
+}
+customElements.define(LessonContainer.is, LessonContainer);
diff --git a/chrome/browser/resources/chromeos/accessibility/common/tutorial/localization.js b/chrome/browser/resources/chromeos/accessibility/common/tutorial/localization.js
index 4db897eb..fd5d03aa 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/tutorial/localization.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/tutorial/localization.js
@@ -24,3 +24,12 @@
     return message;
   },
 };
+
+export class LocalizationInterface {
+  /**
+   * @param {string} id
+   * @param {Array<string>=} opt_subs
+   * @return {string}
+   */
+  getMsg(id, opt_subs) {}
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/common/tutorial/navigation_buttons.js b/chrome/browser/resources/chromeos/accessibility/common/tutorial/navigation_buttons.js
index 1e08c8cd..27fcd8ab6 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/tutorial/navigation_buttons.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/tutorial/navigation_buttons.js
@@ -11,62 +11,81 @@
 import 'chrome://resources/ash/common/cr_elements/cr_shared_style.css.js';
 import 'chrome://resources/ash/common/cr_elements/cr_shared_vars.css.js';
 
-import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Curriculum, Screen} from './constants.js';
-import {Localization} from './localization.js';
+import {Localization, LocalizationInterface} from './localization.js';
 
-export const NavigationButtons = Polymer({
-  is: 'navigation-buttons',
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {LocalizationInterface}
+ */
+const NavigationButtonsBase =
+    mixinBehaviors([Localization], PolymerElement);
 
-  _template: html`{__html_template__}`,
+/** @polymer */
+export class NavigationButtons extends NavigationButtonsBase {
+  static get is() {
+    return 'navigation-buttons';
+  }
 
-  behaviors: [Localization],
+  static get template() {
+    return html`{__html_template__}`;
+  }
 
-  properties: {
-    // Observed properties.
-    /** @type {Screen} */
-    activeScreen: {type: String},
-    /** @type {number} */
-    activeLessonIndex: {type: Number, value: 0},
-    /** @type {Curriculum} */
-    curriculum: {
-      type: String,
-      value: Curriculum.NONE,
-    },
-    /** @type {number} */
-    numLessons: {type: Number, value: 0},
-  },
+  static get properties() {
+    return {
+      // Observed properties.
+      /** @type {Screen} */
+      activeScreen: {type: String},
+      /** @type {number} */
+      activeLessonIndex: {type: Number, value: 0},
+      /** @type {Curriculum} */
+      curriculum: {
+        type: String,
+        value: Curriculum.NONE,
+      },
+      /** @type {number} */
+      numLessons: {type: Number, value: 0},
+    };
+  }
+
+  /** @param {string} eventName */
+  fire(eventName) {
+    this.dispatchEvent(
+        new CustomEvent(eventName, {bubbles: true, composed: true}));
+  }
 
   /** @private */
   onNextLessonButtonClicked_() {
     this.fire('next-lesson-button-clicked');
-  },
+  }
 
   /** @private */
   onPreviousLessonButtonClicked_() {
     this.fire('previous-lesson-button-clicked');
-  },
+  }
 
   /** @private */
   onRestartQuickOrientationButtonClicked_() {
     this.fire('restart-quick-orientation-button-clicked');
-  },
+  }
 
   /** @private */
   onLessonMenuButtonClicked_() {
     this.fire('lesson-menu-button-clicked');
-  },
+  }
 
   /** @private */
   onMainMenuButtonClicked_() {
     this.fire('main-menu-button-clicked');
-  },
+  }
 
   /** @private */
   onExitButtonClicked_() {
     this.fire('exit-button-clicked');
-  },
+  }
 
   /**
    * @param {number} activeLessonIndex
@@ -77,7 +96,7 @@
   shouldHideNextLessonButton_(activeLessonIndex, activeScreen) {
     return activeLessonIndex === this.numLessons - 1 ||
         activeScreen !== Screen.LESSON;
-  },
+  }
 
   /**
    * @param {number} activeLessonIndex
@@ -87,7 +106,7 @@
    */
   shouldHidePreviousLessonButton_(activeLessonIndex, activeScreen) {
     return activeLessonIndex === 0 || activeScreen !== Screen.LESSON;
-  },
+  }
 
   /**
    * @param {Screen} activeScreen
@@ -98,7 +117,7 @@
     return !this.curriculum || this.curriculum === Curriculum.NONE ||
         activeScreen === Screen.MAIN_MENU ||
         activeScreen === Screen.LESSON_MENU || this.numLessons === 1;
-  },
+  }
 
   /**
    * @param {Screen} activeScreen
@@ -107,7 +126,7 @@
    */
   shouldHideMainMenuButton_(activeScreen) {
     return activeScreen === Screen.MAIN_MENU;
-  },
+  }
 
   /**
    * @param {number} activeLessonIndex
@@ -120,7 +139,7 @@
         this.curriculum === Curriculum.QUICK_ORIENTATION &&
         activeLessonIndex === this.numLessons - 1 &&
         this.activeScreen === Screen.LESSON);
-  },
+  }
 
   /**
    * @param {Screen} activeScreen
@@ -129,5 +148,6 @@
    */
   shouldHideNavSeparator_(activeScreen) {
     return activeScreen !== Screen.LESSON;
-  },
-});
+  }
+}
+customElements.define(NavigationButtons.is, NavigationButtons);
diff --git a/chrome/browser/resources/chromeos/app_install/app_install_dialog.html b/chrome/browser/resources/chromeos/app_install/app_install_dialog.html
index 154baa18..521286c 100644
--- a/chrome/browser/resources/chromeos/app_install/app_install_dialog.html
+++ b/chrome/browser/resources/chromeos/app_install/app_install_dialog.html
@@ -89,6 +89,12 @@
     display: block;
   }
 
+  #error-message {
+    color: var(--cros-sys-on_surface);
+    font: var(--cros-body-1-font);
+    padding-top: 14px;
+  }
+
   div[slot=button-container] {
     display: flex;
     justify-content: flex-end;
@@ -105,7 +111,7 @@
   <svg class="title-icon" id="title-icon-installed" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 -960 960 960" width="32"><path d="m437-398 227-226-57-57-170 170-85-85-56 56 141 142ZM40-120v-80h880v80H40Zm120-120q-33 0-56.5-23.5T80-320v-440q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v440q0 33-23.5 56.5T800-240H160Zm0-80h640v-440H160v440Zm0 0v-440 440Z"></svg>
   <svg class="title-icon" id="title-icon-error" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 -960 960 960" width="32"><path d="M480-280q17 0 28.5-11.5T520-320q0-17-11.5-28.5T480-360q-17 0-28.5 11.5T440-320q0 17 11.5 28.5T480-280Zm-40-160h80v-240h-80v240Zm40 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"></svg>
   <h1 id="title">$i18n{installAppToDevice}</h1>
-  <div id="content-card">
+  <div id="content-card" style="display: none">
     <div id="essential-details">
       <img id="app-icon" is="cr-auto-img" width="32" height="32" alt="">
       <div id="name-and-url">
@@ -123,6 +129,7 @@
       </div>
     </div>
   </div>
+  <p id="error-message" style="display: none"></p>
   <div slot="button-container">
     <cros-button
         class="cancel-button"
diff --git a/chrome/browser/resources/chromeos/app_install/app_install_dialog.ts b/chrome/browser/resources/chromeos/app_install/app_install_dialog.ts
index ec14d32..c0bd4321 100644
--- a/chrome/browser/resources/chromeos/app_install/app_install_dialog.ts
+++ b/chrome/browser/resources/chromeos/app_install/app_install_dialog.ts
@@ -22,6 +22,7 @@
   INSTALL = 'install',
   INSTALLING = 'installing',
   INSTALLED = 'installed',
+  ALREADY_INSTALLED = 'already_installed',
   NO_DATA = 'no_data',
   FAILED_INSTALL = 'failed_install',
 }
@@ -31,11 +32,19 @@
     iconIdQuery: string,
     labelId: string,
   };
+  content?: {
+    disabled?: boolean,
+  };
+  errorMessage?: {
+    enabled?: boolean, textId: string,
+  };
   actionButton: {
     disabled?: boolean, labelId: string, handler: (event: MouseEvent) => void,
     handleOnce?: boolean, iconIdQuery: string,
   };
-  cancelButtonLabelId: string;
+  cancelButton: {
+    disabled?: boolean, labelId: string,
+  };
 }
 
 /**
@@ -53,7 +62,7 @@
   }
 
   private proxy = BrowserProxy.getInstance();
-  private initPromise: Promise<boolean>;
+  private initialStatePromise: Promise<DialogState>;
   private dialogStateDataMap: Record<DialogState, StateData>;
 
   constructor() {
@@ -63,7 +72,7 @@
     const fragment = template.content.cloneNode(true);
     this.attachShadow({mode: 'open'}).appendChild(fragment);
 
-    this.initPromise = this.initContent();
+    this.initialStatePromise = this.initContent();
 
     this.dialogStateDataMap = {
       [DialogState.INSTALL]: {
@@ -77,7 +86,9 @@
           handleOnce: true,
           iconIdQuery: '#action-icon-install',
         },
-        cancelButtonLabelId: 'cancel',
+        cancelButton: {
+          labelId: 'cancel',
+        },
       },
       [DialogState.INSTALLING]: {
         title: {
@@ -90,7 +101,10 @@
           handler() {},
           iconIdQuery: '#action-icon-installing',
         },
-        cancelButtonLabelId: 'cancel',
+        cancelButton: {
+          disabled: true,
+          labelId: 'cancel',
+        },
       },
       [DialogState.INSTALLED]: {
         title: {
@@ -102,12 +116,35 @@
           handler: () => this.onOpenAppButtonClick(),
           iconIdQuery: '#action-icon-open-app',
         },
-        cancelButtonLabelId: 'close',
+        cancelButton: {
+          labelId: 'close',
+        },
+      },
+      [DialogState.ALREADY_INSTALLED]: {
+        title: {
+          iconIdQuery: '#title-icon-installed',
+          labelId: 'appAlreadyInstalled',
+        },
+        actionButton: {
+          labelId: 'openApp',
+          handler: () => this.onOpenAppButtonClick(),
+          iconIdQuery: '#action-icon-open-app',
+        },
+        cancelButton: {
+          labelId: 'close',
+        },
       },
       [DialogState.NO_DATA]: {
         title: {
           iconIdQuery: '#title-icon-error',
-          labelId: 'noAppData',
+          labelId: 'noAppDataTitle',
+        },
+        content: {
+          disabled: true,
+        },
+        errorMessage: {
+          enabled: true,
+          textId: 'noAppDataDescription',
         },
         actionButton: {
           labelId: 'tryAgain',
@@ -115,7 +152,9 @@
           handleOnce: true,
           iconIdQuery: '#action-icon-try-again',
         },
-        cancelButtonLabelId: 'cancel',
+        cancelButton: {
+          labelId: 'cancel',
+        },
       },
       [DialogState.FAILED_INSTALL]: {
         title: {
@@ -128,7 +167,9 @@
           handleOnce: true,
           iconIdQuery: '#action-icon-try-again',
         },
-        cancelButtonLabelId: 'cancel',
+        cancelButton: {
+          labelId: 'cancel',
+        },
       },
     };
   }
@@ -138,17 +179,12 @@
     assert(cancelButton);
     cancelButton.addEventListener('click', this.onCancelButtonClick.bind(this));
 
-    const contentCard = this.$<HTMLElement>('#content-card');
-    contentCard.style.visibility = 'hidden';
-
     try {
       const dialogArgs = await this.proxy.handler.getDialogArgs();
       if (!dialogArgs.args) {
-        return false;
+        return DialogState.NO_DATA;
       }
 
-      contentCard.style.visibility = 'visible';
-
       const nameElement = this.$<HTMLParagraphElement>('#name');
       assert(nameElement);
       nameElement.textContent = dialogArgs.args.name;
@@ -177,10 +213,12 @@
             .setAttribute('auto-src', dialogArgs.args.screenshots[0].url.url);
       }
 
-      return true;
+      return dialogArgs.args.isAlreadyInstalled ?
+          DialogState.ALREADY_INSTALLED :
+          DialogState.INSTALL;
     } catch (e) {
       console.error(`Unable to get dialog arguments . Error: ${e}.`);
-      return false;
+      return DialogState.NO_DATA;
     }
   }
 
@@ -193,12 +231,13 @@
   }
 
   async connectedCallback(): Promise<void> {
-    const initSuccess = await this.initPromise;
-    this.changeDialogState(
-        initSuccess ? DialogState.INSTALL : DialogState.NO_DATA);
+    this.changeDialogState(await this.initialStatePromise);
   }
 
   private onCancelButtonClick(): void {
+    if (this.$<Button>('.cancel-button').disabled) {
+      return;
+    }
     this.proxy.handler.closeDialog();
   }
 
@@ -240,6 +279,16 @@
     this.$<HTMLElement>('#title').textContent =
         loadTimeData.getString(data.title.labelId);
 
+    const contentCard = this.$<HTMLElement>('#content-card')!;
+    contentCard.style.display = data.content?.disabled ? 'none' : 'block';
+
+    const errorMessage = this.$<HTMLElement>('#error-message')!;
+    errorMessage.style.display = data.errorMessage?.enabled ? 'block' : 'none';
+    if (data.errorMessage) {
+      errorMessage.textContent =
+          loadTimeData.getString(data.errorMessage.textId);
+    }
+
     const actionButton = this.$<Button>('.action-button')!;
     assert(actionButton);
     actionButton.disabled = Boolean(data.actionButton.disabled);
@@ -256,7 +305,8 @@
 
     const cancelButton = this.$<Button>('.cancel-button');
     assert(cancelButton);
-    cancelButton.label = loadTimeData.getString(data.cancelButtonLabelId);
+    cancelButton.disabled = Boolean(data.cancelButton.disabled);
+    cancelButton.label = loadTimeData.getString(data.cancelButton.labelId);
   }
 }
 
diff --git a/chrome/browser/resources/compose/app.html b/chrome/browser/resources/compose/app.html
index 076d1fc..d95287c 100644
--- a/chrome/browser/resources/compose/app.html
+++ b/chrome/browser/resources/compose/app.html
@@ -503,8 +503,7 @@
           <compose-result-text id="resultText" text-input="[[responseText_]]"
               is-output-complete="{{outputComplete_}}"
               has-output="{{hasOutput_}}"
-              has-partial-output="{{hasPartialOutput_}}"
-              on-result-edit="onResultEdit_">
+              has-partial-output="{{hasPartialOutput_}}">
           </compose-result-text>
           <div id="resultOptions"
               inert$="[[!outputComplete_]]">
diff --git a/chrome/browser/resources/compose/app.ts b/chrome/browser/resources/compose/app.ts
index 3c15782e..78f88a8 100644
--- a/chrome/browser/resources/compose/app.ts
+++ b/chrome/browser/resources/compose/app.ts
@@ -270,7 +270,6 @@
   private hasOutput_: boolean = false;
   private displayedText_: string;
   private responseText_: string;
-  private userResponseText_: string|undefined;
 
   constructor() {
     super();
@@ -288,13 +287,7 @@
   }
 
   private getResponseText_(): TextInput {
-    if (this.userResponseText_ !== undefined) {
-      return {
-        text: this.userResponseText_,
-        isPartial: false,
-        streamingEnabled: false,
-      };
-    } else if (this.response_) {
+    if (this.response_) {
       return {
         text: this.response_.status === ComposeStatus.kOk ?
             this.response_.result.trim() :
@@ -626,7 +619,6 @@
       }
     }
 
-    this.userResponseText_ = undefined;
     const loadingHeight = this.$.loading.offsetHeight;
     this.loading_ = false;
     this.undoEnabled_ = this.response_.undoAvailable;
@@ -759,11 +751,6 @@
         this.response_?.triggeredFromModifier);
   }
 
-  private onResultEdit_(e: CustomEvent<string>) {
-    this.userResponseText_ = e.detail;
-    this.apiProxy_.editResult(this.userResponseText_);
-  }
-
   private saveComposeAppState_() {
     if (this.saveAppStateDebouncer_?.isActive()) {
       this.saveAppStateDebouncer_.flush();
diff --git a/chrome/browser/resources/compose/result_text.html b/chrome/browser/resources/compose/result_text.html
index 134f8730..2b7a3876 100644
--- a/chrome/browser/resources/compose/result_text.html
+++ b/chrome/browser/resources/compose/result_text.html
@@ -21,12 +21,9 @@
 </style>
 <div id="root">
   <div class="result-text" hidden="[[textInput.streamingEnabled]]"
-      content-editable="[[editingEnabled_]]" on-focusout="onFocusOut_"
-      on-input="onInput_">[[textInput.text]]</div>
+      >[[textInput.text]]</div>
   <div id="partialResultText" class="result-text"
       hidden="[[!textInput.streamingEnabled]]"
-      content-editable="[[canEdit_()]]" on-focusout="onFocusOut_"
-      on-input="onInput_"
-    ><template is="dom-repeat" items="[[displayedChunks_]]"
-    ><span>[[item.text]]</span></template></div>
+  ><template is="dom-repeat" items="[[displayedChunks_]]"
+  ><span>[[item.text]]</span></template></div>
 </div>
diff --git a/chrome/browser/resources/compose/result_text.ts b/chrome/browser/resources/compose/result_text.ts
index 43ce0d4..ed8ef6c 100644
--- a/chrome/browser/resources/compose/result_text.ts
+++ b/chrome/browser/resources/compose/result_text.ts
@@ -7,7 +7,6 @@
 import '//resources/cr_elements/cr_shared_vars.css.js';
 import '//resources/cr_elements/cr_icon_button/cr_icon_button.js';
 
-import {loadTimeData} from '//resources/js/load_time_data.js';
 import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './result_text.html.js';
@@ -77,9 +76,6 @@
         type: String,
         readOnly: true,
       },
-      editingEnabled_: {
-        type: Boolean,
-      },
     };
   }
 
@@ -95,14 +91,10 @@
   private wordStreamer_: WordStreamer;
   private displayedChunks_: StreamChunk[] = [];
   private displayedFullText_: string = '';
-  private editingEnabled_: boolean;
-  // Tracking whether the value has changed
-  private isDirty_: boolean = false;
 
   constructor() {
     super();
     this.wordStreamer_ = new WordStreamer(this.setStreamedWords_.bind(this));
-    this.editingEnabled_ = loadTimeData.getBoolean('enableRefinedUi');
   }
 
   updateInputs() {
@@ -129,24 +121,6 @@
     this.wordStreamer_.setCharsPerTickForTesting(5);
   }
 
-  private onFocusOut_() {
-    // Only dispatch event if user has typed something.
-    if (this.editingEnabled_ && this.isDirty_) {
-      this.isDirty_ = false;
-      this.dispatchEvent(new CustomEvent(
-          'result-edit',
-          {bubbles: true, composed: true, detail: this.$.root.innerText}));
-    }
-  }
-
-  private onInput_() {
-    this.isDirty_ = true;
-  }
-
-  private canEdit_() {
-    return this.editingEnabled_ && this.hasOutput && this.isOutputComplete;
-  }
-
   private hasOutput_(): boolean {
     return this.displayedChunks_.length > 0 || this.displayedFullText_ !== '';
   }
diff --git a/chrome/browser/resources/history/app.html b/chrome/browser/resources/history/app.html
index 24a8810..600d407 100644
--- a/chrome/browser/resources/history/app.html
+++ b/chrome/browser/resources/history/app.html
@@ -136,12 +136,14 @@
                 <cr-history-embeddings-filter-chips
                     show-results-by-group="[[
                         getShowResultsByGroup_(selectedPage_)]]"
-                    on-show-results-by-group-changed="onShowResultsByGroupChanged_">
+                    on-show-results-by-group-changed="onShowResultsByGroupChanged_"
+                    on-selected-suggestion-changed="onSelectedSuggestionChanged_">
                 </cr-history-embeddings-filter-chips>
                 <template is="dom-if" restamp
                     if="[[shouldShowHistoryEmbeddings_(queryState_.searchTerm)]]">
                   <cr-history-embeddings
-                      search-query="[[queryState_.searchTerm]]">
+                      search-query="[[queryState_.searchTerm]]"
+                      time-range-start="[[timeRangeStart_]]">
                   </cr-history-embeddings>
                 </template>
               </div>
diff --git a/chrome/browser/resources/history/app.ts b/chrome/browser/resources/history/app.ts
index c3f13b5..6a9b81ef 100644
--- a/chrome/browser/resources/history/app.ts
+++ b/chrome/browser/resources/history/app.ts
@@ -19,6 +19,7 @@
 import './side_bar.js';
 import './strings.m.js';
 
+import type {Suggestion} from 'chrome://resources/cr_components/history_embeddings/filter_chips.js';
 import type {CrDrawerElement} from 'chrome://resources/cr_elements/cr_drawer/cr_drawer.js';
 import type {CrLazyRenderElement} from 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
 import type {FindShortcutMixinInterface} from 'chrome://resources/cr_elements/find_shortcut_mixin.js';
@@ -268,6 +269,7 @@
   private toolbarShadow_: boolean;
   private historyClustersViewStartTime_: Date|null = null;
   private scrollTarget_: HTMLElement;
+  private timeRangeStart_?: Date;
 
   constructor() {
     super();
@@ -657,6 +659,10 @@
                .filter(part => part.length > 0)
                .length > 1;
   }
+
+  private onSelectedSuggestionChanged_(e: CustomEvent<{value: Suggestion}>) {
+    this.timeRangeStart_ = e.detail.value?.timeRangeStart;
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/lens/overlay/BUILD.gn b/chrome/browser/resources/lens/overlay/BUILD.gn
index 94d0181..c41b338 100644
--- a/chrome/browser/resources/lens/overlay/BUILD.gn
+++ b/chrome/browser/resources/lens/overlay/BUILD.gn
@@ -16,6 +16,7 @@
 
   web_component_files = [
     "lens_overlay_app.ts",
+    "object_layer.ts",
     "region_selection.ts",
     "selection_overlay.ts",
     "side_panel/side_panel_app.ts",
diff --git a/chrome/browser/resources/lens/overlay/object_layer.html b/chrome/browser/resources/lens/overlay/object_layer.html
new file mode 100644
index 0000000..252e16f
--- /dev/null
+++ b/chrome/browser/resources/lens/overlay/object_layer.html
@@ -0,0 +1,9 @@
+<style>
+  .object {
+    cursor: pointer;
+    position: absolute;
+  }
+</style>
+<template id="objectsContainer" is="dom-repeat" items="[[renderedObjects]]">
+  <div style$="[[getObjectStyle(item)]]" class="object"></div>
+</template>
diff --git a/chrome/browser/resources/lens/overlay/object_layer.ts b/chrome/browser/resources/lens/overlay/object_layer.ts
new file mode 100644
index 0000000..fe54e4c
--- /dev/null
+++ b/chrome/browser/resources/lens/overlay/object_layer.ts
@@ -0,0 +1,178 @@
+// 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 {assert} from '//resources/js/assert.js';
+import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import type {DomRepeat} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {BrowserProxyImpl} from './browser_proxy.js';
+import {CenterRotatedBox_CoordinateType} from './geometry.mojom-webui.js';
+import type {LensPageCallbackRouter} from './lens.mojom-webui.js';
+import {getTemplate} from './object_layer.html.js';
+import type {OverlayObject} from './overlay_object.mojom-webui.js';
+import type {GestureEvent} from './selection_utils.js';
+
+// Takes the value between 0-1 and returns a string in the from '__%';
+function toPercent(value: number): string {
+  return `${value * 100}%`;
+}
+
+// Returns true if the object has a valid bounding box and is renderable by the
+// ObjectLayer.
+function isObjectRenderable(object: OverlayObject): boolean {
+  // For an object to be renderable, it must have a bounding box with normalized
+  // coordinates.
+  // TODO(b/330183480): Add rendering for IMAGE CoordinateType
+  const objectBoundingBox = object.geometry?.boundingBox;
+  if (!objectBoundingBox) {
+    return false;
+  }
+
+  return objectBoundingBox.coordinateType ===
+      CenterRotatedBox_CoordinateType.kNormalized;
+}
+
+// Orders objects with larger areas before objects with smaller areas.
+function compareArea(object1: OverlayObject, object2: OverlayObject): number {
+  assert(object1.geometry);
+  assert(object2.geometry);
+  return object2.geometry.boundingBox.box.width *
+      object2.geometry.boundingBox.box.height -
+      object1.geometry.boundingBox.box.width *
+      object1.geometry.boundingBox.box.height;
+}
+
+export interface ObjectLayerElement {
+  $: {
+    objectsContainer: DomRepeat,
+  };
+}
+
+/*
+ * Element responsible for highlighting and selection text.
+ */
+export class ObjectLayerElement extends PolymerElement {
+  static get is() {
+    return 'lens-object-layer';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      renderedObjects: {
+        type: Array,
+        value: () => [],
+      },
+    };
+  }
+
+  // The objects rendered in this layer.
+  private renderedObjects: OverlayObject[];
+
+  private readonly router: LensPageCallbackRouter =
+      BrowserProxyImpl.getInstance().callbackRouter;
+  private objectsReceivedListenerId: number|null = null;
+
+  override connectedCallback() {
+    super.connectedCallback();
+
+    // Set up listener to receive objects from C++.
+    this.objectsReceivedListenerId = this.router.objectsReceived.addListener(
+        this.onObjectsReceived.bind(this));
+  }
+
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+
+    // Remove listener to receive objects from C++.
+    assert(this.objectsReceivedListenerId);
+    this.router.removeListener(this.objectsReceivedListenerId);
+    this.objectsReceivedListenerId = null;
+  }
+
+  handleUpGesture(event: GestureEvent): boolean {
+    const objectIndex = this.objectIndexFromPoint(event.clientX, event.clientY);
+    // Ignore if the click is not on an object.
+    if (objectIndex === null) {
+      return false;
+    }
+
+    BrowserProxyImpl.getInstance().handler.issueLensRequest(
+        this.renderedObjects[objectIndex].geometry!.boundingBox);
+    return true;
+  }
+
+  private onObjectsReceived(objects: OverlayObject[]) {
+    // Sort objects by descending bounding box areas so that smaller objects
+    // are rendered over, and take priority over, larger objects.
+    this.renderedObjects =
+        objects.filter(o => isObjectRenderable(o)).sort(compareArea);
+  }
+
+  /** @return The CSS styles string for the given object. */
+  private getObjectStyle(object: OverlayObject): string {
+    // Objects without bounding boxes are filtered out, so guaranteed that
+    // geometry is not null.
+    const objectBoundingBox = object.geometry!.boundingBox;
+
+    // TODO(b/330183480): Currently, we are assuming that object
+    // coordinates are normalized. We should still implement
+    // rendering in case this assumption is ever violated.
+    if (objectBoundingBox.coordinateType !==
+        CenterRotatedBox_CoordinateType.kNormalized) {
+      return '';
+    }
+
+    // Put into an array instead of a long string to keep this code readable.
+    const styles: string[] = [
+      `width: ${toPercent(objectBoundingBox.box.width)}`,
+      `height: ${toPercent(objectBoundingBox.box.height)}`,
+      `top: ${
+          toPercent(
+              objectBoundingBox.box.y - (objectBoundingBox.box.height / 2))}`,
+      `left: ${
+          toPercent(
+              objectBoundingBox.box.x - (objectBoundingBox.box.width / 2))}`,
+      `transform: rotate(${objectBoundingBox.rotation}rad)`,
+    ];
+    return styles.join(';');
+  }
+
+  /**
+   * @return Returns the index in renderedObjects of the object at the given
+   *     point. Returns null if no object is at the given point.
+   */
+  private objectIndexFromPoint(x: number, y: number): number|null {
+    // Find the top-most element at the clicked point that is an object.
+    // elementFromPoint() may select non-object elements that have a higher
+    // z-index.
+    const elementsAtPoint = this.shadowRoot!.elementsFromPoint(x, y);
+    for (const element of elementsAtPoint) {
+      if (!(element instanceof HTMLElement)) {
+        continue;
+      }
+      const index = this.$.objectsContainer.indexForElement(element);
+      if (index !== null) {
+        return index;
+      }
+    }
+    return null;
+  }
+
+  // Testing method to get the objects on the page.
+  getObjectNodesForTesting() {
+    return this.shadowRoot!.querySelectorAll<HTMLElement>('.object');
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'lens-object-layer': ObjectLayerElement;
+  }
+}
+
+customElements.define(ObjectLayerElement.is, ObjectLayerElement);
diff --git a/chrome/browser/resources/lens/overlay/selection_overlay.html b/chrome/browser/resources/lens/overlay/selection_overlay.html
index 749b570..de861b5 100644
--- a/chrome/browser/resources/lens/overlay/selection_overlay.html
+++ b/chrome/browser/resources/lens/overlay/selection_overlay.html
@@ -28,6 +28,7 @@
   background image. -->
   <div id="selectionElements">
     <!-- Other elements that need to be bounded to the image go here -->
+    <lens-object-layer id="objectSelectionLayer"></lens-object-layer>
     <region-selection id="regionSelectionLayer"></region-selection>
     <lens-text-layer id="textSelectionLayer"></lens-text-layer>
   </div>
diff --git a/chrome/browser/resources/lens/overlay/selection_overlay.ts b/chrome/browser/resources/lens/overlay/selection_overlay.ts
index d7b9871..197facd 100644
--- a/chrome/browser/resources/lens/overlay/selection_overlay.ts
+++ b/chrome/browser/resources/lens/overlay/selection_overlay.ts
@@ -2,11 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './object_layer.js';
 import './text_layer.js';
 import './region_selection.js';
 
 import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import type {ObjectLayerElement} from './object_layer.js';
 import type {RegionSelectionElement} from './region_selection.js';
 import {getTemplate} from './selection_overlay.html.js';
 import {DRAG_THRESHOLD, DragFeature, emptyGestureEvent, type GestureEvent, GestureState} from './selection_utils.js';
@@ -17,6 +19,7 @@
     regionSelectionLayer: RegionSelectionElement,
     selectionOverlay: HTMLElement,
     textSelectionLayer: TextLayerElement,
+    objectSelectionLayer: ObjectLayerElement,
   };
 }
 
@@ -109,6 +112,7 @@
         break;
       case GestureState.STARTING:
         // This gesture was a tap. Let the features respond to a tap.
+        this.$.objectSelectionLayer.handleUpGesture(this.currentGesture);
         break;
       default:  // Other states are invalid and ignored.
         break;
diff --git a/chrome/browser/resources/new_tab_page/BUILD.gn b/chrome/browser/resources/new_tab_page/BUILD.gn
index 9b395215..ea29038 100644
--- a/chrome/browser/resources/new_tab_page/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/BUILD.gn
@@ -31,7 +31,6 @@
     "$target_gen_dir/modules/feed/icons/resources.grdp",
     "$target_gen_dir/modules/photos/icons/resources.grdp",
     "$target_gen_dir/modules/photos/images/resources.grdp",
-    "$target_gen_dir/modules/recipes/icons/resources.grdp",
   ]
 
   extra_grdp_deps = [
@@ -40,7 +39,6 @@
     "modules/feed/icons:build_grdp",
     "modules/photos/icons:build_grdp",
     "modules/photos/images:build_grdp",
-    "modules/recipes/icons:build_grdp",
   ]
 
   if (optimize_webui) {
diff --git a/chrome/browser/resources/new_tab_page/app.html b/chrome/browser/resources/new_tab_page/app.html
index 13e88951..488afac 100644
--- a/chrome/browser/resources/new_tab_page/app.html
+++ b/chrome/browser/resources/new_tab_page/app.html
@@ -156,24 +156,27 @@
   }
 
   #wallpaperSearchButton {
-    --hover-bg-color:
+    --cr-hover-background-color:
         var(--color-new-tab-page-wallpaper-search-button-background-hovered);
-    --text-color: var(--color-new-tab-page-wallpaper-search-button-foreground);
-    background-color:
+    --cr-button-text-color:
+        var(--color-new-tab-page-wallpaper-search-button-foreground);
+    --cr-button-background-color:
         var(--color-new-tab-page-wallpaper-search-button-background);
   }
 
   #customizeButton {
-    --hover-bg-color: var(--color-new-tab-page-button-background-hovered);
-    --text-color: var(--color-new-tab-page-button-foreground);
-    background-color: var(--color-new-tab-page-button-background);
+    --cr-hover-background-color:
+        var(--color-new-tab-page-button-background-hovered);
+    --cr-button-text-color: var(--color-new-tab-page-button-foreground);
+    --cr-button-background-color: var(--color-new-tab-page-button-background);
   }
 
   :host([show-background-image_]) #customizeButton,
   :host([show-background-image_]) #wallpaperSearchButton {
-    --hover-bg-color: var(--ntp-protected-icon-background-color-hovered);
-    --text-color: white;
-    background-color: var(--ntp-protected-icon-background-color);
+    --cr-hover-background-color:
+        var(--ntp-protected-icon-background-color-hovered);
+    --cr-button-text-color: white;
+    --cr-button-background-color: var(--ntp-protected-icon-background-color);
   }
 
   #customizeButton:has(help-bubble) {
@@ -214,7 +217,7 @@
 
   .customize-icon {
     --cr-icon-button-margin-start: 0;
-    --cr-icon-color: var(--text-color);
+    --cr-icon-color: var(--cr-button-text-color);
     --cr-icon-ripple-margin: 0;
     --cr-icon-ripple-size: 16px;
     --cr-icon-size: 100%;
@@ -297,7 +300,7 @@
   while the icon has a color rule of it's own. */
   @keyframes color-text {
     from { color: rgba(0,0,0,0); }
-    to { color: var(--text-color); }
+    to { color: var(--cr-button-text-color); }
   }
 
   :host([wallpaper-search-button-animation-enabled_])
diff --git a/chrome/browser/resources/new_tab_page/lazy_load.ts b/chrome/browser/resources/new_tab_page/lazy_load.ts
index d1c3c27..ebdbc36e 100644
--- a/chrome/browser/resources/new_tab_page/lazy_load.ts
+++ b/chrome/browser/resources/new_tab_page/lazy_load.ts
@@ -46,8 +46,7 @@
 export {DisableModuleEvent, DismissModuleEvent, ModulesElement} from './modules/modules.js';
 export {photosDescriptor, PhotosModuleElement} from './modules/photos/module.js';
 export {PhotosProxy} from './modules/photos/photos_module_proxy.js';
-export {RecipesModuleElement, recipeTasksDescriptor} from './modules/recipes/module.js';
-export {RecipesHandlerProxy} from './modules/recipes/recipes_handler_proxy.js';
+export {googleCalendarDescriptor, CalendarModuleElement} from './modules/v2/calendar/module.js';
 // <if expr="not is_official_build">
 export {FooProxy} from './modules/v2/dummy/foo_proxy.js';
 export {DummyModuleElement, dummyV2Descriptor} from './modules/v2/dummy/module.js';
diff --git a/chrome/browser/resources/new_tab_page/modules/cart/discount_consent_card.html b/chrome/browser/resources/new_tab_page/modules/cart/discount_consent_card.html
index 9666f0fe..0e119cb8 100644
--- a/chrome/browser/resources/new_tab_page/modules/cart/discount_consent_card.html
+++ b/chrome/browser/resources/new_tab_page/modules/cart/discount_consent_card.html
@@ -52,8 +52,8 @@
   }
 
   .action-button {
-    --text-color-action: var(--color-new-tab-page-action-button-foreground);
-    background-color: var(--color-new-tab-page-action-button-background);
+    --cr-button-text-color: var(--color-new-tab-page-action-button-foreground);
+    --cr-button-background-color: var(--color-new-tab-page-action-button-background);
   }
 
   .content-container {
diff --git a/chrome/browser/resources/new_tab_page/modules/cart/module.html b/chrome/browser/resources/new_tab_page/modules/cart/module.html
index c99b5c5..5055275 100644
--- a/chrome/browser/resources/new_tab_page/modules/cart/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/cart/module.html
@@ -171,8 +171,8 @@
   }
 
   .action-button {
-    --text-color-action: var(--color-new-tab-page-action-button-foreground);
-    background-color: var(--color-new-tab-page-action-button-background);
+    --cr-button-text-color: var(--color-new-tab-page-action-button-foreground);
+    --cr-button-background-color: var(--color-new-tab-page-action-button-background);
   }
 
   .cart-title {
diff --git a/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts b/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts
index 9b7b93884..84f2be5 100644
--- a/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts
+++ b/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts
@@ -17,7 +17,7 @@
 import type {ModuleDescriptor} from './module_descriptor.js';
 import {ModuleRegistry} from './module_registry.js';
 import {photosDescriptor} from './photos/module.js';
-import {recipeTasksDescriptor} from './recipes/module.js';
+import {googleCalendarDescriptor} from './v2/calendar/module.js';
 // <if expr="not is_official_build">
 import {dummyV2Descriptor} from './v2/dummy/module.js';
 // </if>
@@ -28,7 +28,6 @@
 const modulesRedesignedEnabled: boolean =
     loadTimeData.getBoolean('modulesRedesignedEnabled');
 export const descriptors: ModuleDescriptor[] = [];
-descriptors.push(recipeTasksDescriptor);
 descriptors.push(chromeCartDescriptor);
 descriptors.push(
     modulesRedesignedEnabled ? fileSuggestionDescriptor : driveDescriptor);
@@ -39,6 +38,7 @@
                                historyClustersDescriptor);
 
 descriptors.push(tabResumptionDescriptor);
+descriptors.push(googleCalendarDescriptor);
 
 // <if expr="not is_official_build">
 if (modulesRedesignedEnabled) {
diff --git a/chrome/browser/resources/new_tab_page/modules/modules.gni b/chrome/browser/resources/new_tab_page/modules/modules.gni
index ca25779..2f40966 100644
--- a/chrome/browser/resources/new_tab_page/modules/modules.gni
+++ b/chrome/browser/resources/new_tab_page/modules/modules.gni
@@ -7,7 +7,7 @@
 import("./feed/feed.gni")
 import("./history_clusters/history_clusters.gni")
 import("./photos/photos.gni")
-import("./recipes/recipes.gni")
+import("./v2/calendar/calendar.gni")
 import("./v2/file_suggestion/file_suggestion.gni")
 import("./v2/history_clusters/history_clusters.gni")
 import("./v2/tab_resumption/tab_resumption.gni")
@@ -24,7 +24,7 @@
       "modules/module_registry.ts",
     ] + cart_non_web_component_files + drive_non_web_component_files +
     feed_non_web_component_files + photos_non_web_component_files +
-    recipes_non_web_component_files + history_clusters_non_web_component_files +
+    history_clusters_non_web_component_files +
     history_clusters_v2_non_web_component_files +
     tab_resumption_v2_non_web_component_files
 
@@ -41,9 +41,9 @@
       "modules/module_wrapper.ts",
       "modules/v2/module_header.ts",
       "modules/v2/modules.ts",
-    ] + cart_web_component_files + drive_web_component_files +
-    file_suggestion_v2_web_component_files + feed_web_component_files +
-    photos_web_component_files + recipes_web_component_files +
+    ] + calendar_v2_web_component_files + cart_web_component_files +
+    drive_web_component_files + feed_web_component_files +
+    file_suggestion_v2_web_component_files + photos_web_component_files +
     history_clusters_web_component_files +
     history_clusters_v2_web_component_files +
     tab_resumption_v2_web_component_files
diff --git a/chrome/browser/resources/new_tab_page/modules/modules.html b/chrome/browser/resources/new_tab_page/modules/modules.html
index d4a812f..89bf0bb0 100644
--- a/chrome/browser/resources/new_tab_page/modules/modules.html
+++ b/chrome/browser/resources/new_tab_page/modules/modules.html
@@ -60,14 +60,14 @@
   }
 
   .action-button {
-    --text-color-action: var(--color-new-tab-page-action-button-foreground);
-    background-color: var(--color-new-tab-page-action-button-background);
+    --cr-button-text-color: var(--color-new-tab-page-action-button-foreground);
+    --cr-button-background-color: var(--color-new-tab-page-action-button-background);
     margin-inline-end: 8px;
     margin-top: 18px;
   }
 
   .cancel-button {
-    --text-color: var(--color-new-tab-page-button-foreground);
+    --cr-button-text-color: var(--color-new-tab-page-button-foreground);
   }
 
   ntp-module-wrapper {
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes/icons/BUILD.gn b/chrome/browser/resources/new_tab_page/modules/recipes/icons/BUILD.gn
deleted file mode 100644
index 1dd9718..0000000
--- a/chrome/browser/resources/new_tab_page/modules/recipes/icons/BUILD.gn
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//ui/webui/resources/tools/generate_grd.gni")
-
-assert(!is_android)
-
-generate_grd("build_grdp") {
-  grd_prefix = "recipes"
-  out_grd = "$target_gen_dir/resources.grdp"
-  input_files = [ "recipes_logo.svg" ]
-  input_files_base_dir = rebase_path(".", "//")
-  resource_path_prefix = "modules/recipes/icons"
-}
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes/icons/recipes_logo.svg b/chrome/browser/resources/new_tab_page/modules/recipes/icons/recipes_logo.svg
deleted file mode 100644
index 9e4006d2..0000000
--- a/chrome/browser/resources/new_tab_page/modules/recipes/icons/recipes_logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="#455A64"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 6v8h3v8h2V2c-2.76 0-5 2.24-5 4zm-5 3H9V2H7v7H5V2H3v7c0 2.21 1.79 4 4 4v9h2v-9c2.21 0 4-1.79 4-4V2h-2v7z"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes/module.html b/chrome/browser/resources/new_tab_page/modules/recipes/module.html
deleted file mode 100644
index 576af56..0000000
--- a/chrome/browser/resources/new_tab_page/modules/recipes/module.html
+++ /dev/null
@@ -1,227 +0,0 @@
-<style include="cr-hidden-style">
-  :host {
-    display: flex;
-    flex-direction: column;
-    height: 100%;
-    width: 100%;
-  }
-
-  :host([overflow-scroll_]) #container {
-    overflow-x: auto;
-  }
-
-  :host([overflow-scroll_]) #content {
-    padding-top: 2px;
-    /* 561px - 2px to account for module wrapper border. */
-    width: 559px;
-  }
-
-  :host([overflow-scroll_][wide-modules-enabled_]) #content {
-    /* 768px - 2px to account for module wrapper border. */
-    width: 766px;
-  }
-
-  #content {
-    box-sizing: border-box;
-    display: block;
-    flex-grow: 1;
-    padding-bottom: 16px;
-    padding-inline-end: 16px;
-    padding-inline-start: 16px;
-  }
-
-  #recipes {
-    display: flex;
-    flex-direction: row;
-    justify-content: space-between;
-  }
-
-  .recipe {
-    border-radius: 4px;
-    display: flex;
-    flex-direction: column;
-    outline: none;
-    position: relative;
-    text-decoration: none;
-    width: 165px;
-  }
-
-  :host-context(.focus-outline-visible) .recipe:focus {
-    box-shadow: var(--ntp-focus-shadow);
-  }
-
-  .recipe:not([hidden]) + .recipe {
-    margin-inline-start: 16px;
-  }
-
-  .image-background {
-    /* Mixes to Google Grey 50 underneath .image-container. */
-    background-color: rgb(22, 55, 88);
-    border-radius: 4px;
-    height: 120px;
-    margin-bottom: 8px;
-    width: inherit;
-  }
-
-  .image-container {
-    background-color: white;
-    border-radius: 4px;
-    /* Using box-shadow mimics proper rendering,
-     * so the color of the image-background may not be seen
-     * after rounding edges. */
-    box-shadow: 0 0 0 0.2px white;
-    box-sizing: border-box;
-    height: 100%;
-    opacity: 97%;
-    padding: 10px;
-  }
-
-  .recipe img {
-    border-radius: 4px;
-    height: 136px;
-    margin-bottom: 8px;
-    object-fit: cover;
-    width: inherit;
-  }
-
-  .tag {
-    background: var(--color-new-tab-page-tag-background);
-    border-radius: 4px;
-    box-sizing: border-box;
-    color: var(--color-new-tab-page-primary-foreground);
-    font-size: 11px;
-    margin: 8px;
-    max-width: 149px;
-    overflow: hidden;
-    padding:  8px;
-    position: absolute;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-  }
-
-  :host-context([dir=rtl]) .tag {
-    right: 0;
-  }
-
-  .price {
-    color: var(--color-new-tab-page-primary-foreground);
-    font-size: 13px;
-    font-weight: bold;
-    height: 14px;
-    line-height: 15px;
-    margin-bottom: 8px;
-  }
-
-  .name {
-    color: var(--color-new-tab-page-primary-foreground);
-    font-size: 12px;
-    line-height: 20px;
-    margin-bottom: 4px;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-  }
-
-  .secondary {
-    color: var(--color-new-tab-page-secondary-foreground);
-    font-size: 11px;
-    height: 13px;
-    text-overflow: ellipsis;
-  }
-
-  #relatedSearches {
-    display: flex;
-    flex-direction: row;
-    margin-top: 16px;
-  }
-
-  .pill {
-    align-items: center;
-    border: solid var(--color-new-tab-page-module-control-border) 1px;
-    border-radius: 16px;
-    box-sizing: border-box;
-    display: flex;
-    flex-direction: row;
-    flex-shrink: 0;
-    height: 32px;
-    outline: none;
-    text-decoration: none;
-  }
-
-  .pill:hover {
-    background-color: var(--color-new-tab-page-control-background-hovered);
-  }
-
-  .pill:active {
-    background-color: var(--color-new-tab-page-active-background);
-  }
-
-  :host-context(.focus-outline-visible) .pill:focus {
-    box-shadow: var(--ntp-focus-shadow);
-  }
-
-  .pill + .pill {
-    margin-inline-start: 8px;
-  }
-
-  .clock {
-    -webkit-mask-image: url(chrome://resources/images/icon_clock.svg);
-    -webkit-mask-repeat: no-repeat;
-    -webkit-mask-size: 100%;
-    background-color: var(--color-new-tab-page-secondary-foreground);
-    height: 16px;
-    margin-inline-start: 12px;
-    width: 16px;
-  }
-
-  .search-text {
-    color: var(--color-new-tab-page-primary-foreground);
-    font-size: 13px;
-    margin-inline-end: 12px;
-    margin-inline-start: 8px;
-  }
-</style>
-<ntp-module-header
-    dismiss-text="[[i18n('modulesDismissButtonText', dismissName_)]]"
-    disable-text="[[i18n('modulesDisableButtonText', disableName_)]]"
-    more-actions-text="[[i18n('modulesMoreActions', disableName_)]]"
-    show-info-button on-info-button-click="onInfoButtonClick_"
-    show-dismiss-button on-dismiss-button-click="onDismissButtonClick_"
-    on-disable-button-click="onDisableButtonClick_"
-    icon-src="modules/recipes/icons/recipes_logo.svg">
-  [[title_]]
-</ntp-module-header>
-<div id="container">
-  <div id="content">
-    <div id="recipes">
-      <template is="dom-repeat" id="recipesRepeat" on-dom-change="onDomChange_"
-          items="[[getRecipes_(task, wideModulesLoaded_)]]">
-        <a class="recipe" href="[[item.targetUrl.url]]"
-            on-click="onRecipeClick_" on-auxclick="onRecipeClick_">
-          <img is="cr-auto-img" auto-src="[[item.imageUrl.url]]"
-              draggable="false">
-          </img>
-          <div class="tag" title="[[item.info]]">[[item.info]]</div>
-          <div class="name" title="[[item.name]]">[[item.name]]</div>
-          <div class="secondary">[[item.siteName]]</div>
-        </a>
-      </template>
-    </div>
-    <div hidden$="{{!showRelatedSearches_}}" id="relatedSearches">
-      <template is="dom-repeat" id="relatedSearchesRepeat"
-          items="[[task.relatedSearches]]" on-dom-change="onDomChange_">
-        <a class="pill" href="[[item.targetUrl.url]]" on-click="onPillClick_"
-            on-auxclick="onPillClick_">
-          <div class="clock"></div>
-          <div class="search-text">[[item.text]]</div>
-        </a>
-      </template>
-    </div>
-  </div>
-</div>
-<cr-lazy-render id="infoDialogRender">
-  <template>
-    <ntp-info-dialog inner-h-t-m-l="[[info_]]">
-    </ntp-info-dialog>
-  </template>
-</cr-lazy-render>
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes/module.ts b/chrome/browser/resources/new_tab_page/modules/recipes/module.ts
deleted file mode 100644
index 9facfd48..0000000
--- a/chrome/browser/resources/new_tab_page/modules/recipes/module.ts
+++ /dev/null
@@ -1,221 +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 '../module_header.js';
-import 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
-import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
-import 'chrome://resources/cr_elements/cr_hidden_style.css.js';
-
-import type {CrLazyRenderElement} from 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
-import type {DomRepeat, DomRepeatEvent} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {I18nMixin, loadTimeData} from '../../i18n_setup.js';
-import type {Recipe, RelatedSearch, Task} from '../../recipes.mojom-webui.js';
-import type {InfoDialogElement} from '../info_dialog.js';
-import {ModuleDescriptor} from '../module_descriptor.js';
-
-import {getTemplate} from './module.html.js';
-import {RecipesHandlerProxy} from './recipes_handler_proxy.js';
-
-export interface RecipesModuleElement {
-  $: {
-    infoDialogRender: CrLazyRenderElement<InfoDialogElement>,
-    relatedSearchesRepeat: DomRepeat,
-    recipesRepeat: DomRepeat,
-  };
-}
-
-/**
- * Implements the UI of a recipe module. This module shows a currently active
- * recipe search journey and provides a way for the user to continue that search
- * journey.
- */
-export class RecipesModuleElement extends I18nMixin
-(PolymerElement) {
-  static get is() {
-    return 'ntp-recipe-module';
-  }
-
-  static get template() {
-    return getTemplate();
-  }
-
-  static get properties() {
-    return {
-      task: Object,
-
-      showRelatedSearches_: {
-        type: Boolean,
-        computed: 'computeShowRelatedSearches_(task)',
-      },
-
-      title_: {
-        type: String,
-        computed: 'computeTitle_()',
-      },
-
-      dismissName_: {
-        type: String,
-        computed: 'computeDismissName_()',
-      },
-
-      disableName_: {
-        type: String,
-        computed: 'computeDisableName_()',
-      },
-
-      info_: {
-        type: String,
-        computed: 'computeInfo_()',
-      },
-
-      overflowScroll_: {
-        type: Boolean,
-        value: () => loadTimeData.getBoolean('modulesOverflowScrollbarEnabled'),
-        reflectToAttribute: true,
-      },
-
-      wideModulesEnabled_: {
-        type: Boolean,
-        value: () => loadTimeData.getBoolean('wideModulesEnabled'),
-        reflectToAttribute: true,
-      },
-    };
-  }
-
-  task: Task;
-  private showRelatedSearches_: boolean;
-  private title_: string;
-  private dismissName_: string;
-  private disableName_: string;
-  private info_: string;
-  private overflowScroll_: boolean;
-  private wideModulesEnabled_: boolean;
-  private intersectionObserver_: IntersectionObserver|null = null;
-
-  private computeTitle_(): string {
-    return loadTimeData.getBoolean('modulesRecipeHistoricalExperimentEnabled') ?
-        loadTimeData.getString('modulesRecipeViewedTasksSentence') :
-        loadTimeData.getString('modulesRecipeTasksSentence');
-  }
-
-  private computeDismissName_(): string {
-    return loadTimeData.getBoolean('modulesRecipeHistoricalExperimentEnabled') ?
-        loadTimeData.getString('modulesRecipeViewedTasksLowerThese') :
-        loadTimeData.getString('modulesRecipeTasksLowerThese');
-  }
-
-  private computeDisableName_(): string {
-    return loadTimeData.getBoolean('modulesRecipeHistoricalExperimentEnabled') ?
-        loadTimeData.getString('modulesRecipeViewedTasksLower') :
-        loadTimeData.getString('modulesRecipeTasksLower');
-  }
-
-  private computeInfo_(): TrustedHTML {
-    return loadTimeData.getBoolean('moduleRecipeExtendedExperimentEnabled') ?
-        this.i18nAdvanced('modulesRecipeExtendedInfo') :
-        this.i18nAdvanced('modulesRecipeInfo');
-  }
-
-  private getRecipes_(): Recipe[] {
-    return this.task.recipes.slice(0, this.wideModulesEnabled_ ? 4 : 3);
-  }
-
-  private computeShowRelatedSearches_(): boolean {
-    return this.task.relatedSearches && this.task.relatedSearches.length > 0;
-  }
-
-  private onRecipeClick_(e: DomRepeatEvent<Recipe>) {
-    const index = e.model.index;
-    RecipesHandlerProxy.getHandler().onRecipeClicked(index);
-    this.dispatchEvent(new Event('usage', {bubbles: true, composed: true}));
-  }
-
-  private onPillClick_(e: DomRepeatEvent<RelatedSearch>) {
-    const index = e.model.index;
-    RecipesHandlerProxy.getHandler().onRelatedSearchClicked(index);
-    this.dispatchEvent(new Event('usage', {bubbles: true, composed: true}));
-  }
-
-  private onInfoButtonClick_() {
-    this.$.infoDialogRender.get().showModal();
-  }
-
-  private onDismissButtonClick_() {
-    RecipesHandlerProxy.getHandler().dismissTask(this.task.name);
-    this.dispatchEvent(new CustomEvent('dismiss-module', {
-      bubbles: true,
-      composed: true,
-      detail: {
-        message:
-            loadTimeData.getStringF('dismissModuleToastMessage', this.title_),
-        restoreCallback: this.onRestore_.bind(this),
-      },
-    }));
-  }
-
-  private onDisableButtonClick_() {
-    this.dispatchEvent(new CustomEvent('disable-module', {
-      bubbles: true,
-      composed: true,
-      detail: {
-        message: loadTimeData.getStringF(
-            'disableModuleToastMessage', this.disableName_),
-      },
-    }));
-  }
-
-  private onRestore_() {
-    RecipesHandlerProxy.getHandler().restoreTask(this.task.name);
-  }
-
-  private onDomChange_() {
-    if (!this.intersectionObserver_) {
-      this.intersectionObserver_ = new IntersectionObserver(entries => {
-        entries.forEach(({intersectionRatio, target}) => {
-          if (this.overflowScroll_) {
-            (target as HTMLElement).style.display =
-                (intersectionRatio < 1) ? 'none' : 'flex';
-          } else {
-            (target as HTMLElement).style.visibility =
-                intersectionRatio < 1 ? 'hidden' : 'visible';
-          }
-        });
-
-        if (this.overflowScroll_) {
-          // Disconnect the intersection observer for a11y reasons so that
-          // subsequent viewport changes do not remove items from display.
-          this.intersectionObserver_!.disconnect();
-        }
-        this.dispatchEvent(new Event('visibility-update'));
-      }, {root: this, threshold: 1});
-    } else {
-      this.intersectionObserver_.disconnect();
-    }
-
-    const observeClasses = ['.pill'];
-    if (!this.overflowScroll_) {
-      observeClasses.push('.recipe');
-    }
-    this.shadowRoot!.querySelectorAll(observeClasses.join(','))
-        .forEach(el => this.intersectionObserver_!.observe(el));
-  }
-}
-
-customElements.define(RecipesModuleElement.is, RecipesModuleElement);
-
-async function createModule(): Promise<HTMLElement|null> {
-  const {task} = await RecipesHandlerProxy.getHandler().getPrimaryTask();
-  if (!task) {
-    return null;
-  }
-
-  const element = new RecipesModuleElement();
-  element.task = task;
-  return element;
-}
-
-export const recipeTasksDescriptor: ModuleDescriptor =
-    new ModuleDescriptor(/*id=*/ 'recipe_tasks', createModule);
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes/recipes.gni b/chrome/browser/resources/new_tab_page/modules/recipes/recipes.gni
deleted file mode 100644
index b996da9c..0000000
--- a/chrome/browser/resources/new_tab_page/modules/recipes/recipes.gni
+++ /dev/null
@@ -1,9 +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.
-
-# List of files that don't need to be passed to html_to_wrapper().
-recipes_non_web_component_files = [ "modules/recipes/recipes_handler_proxy.ts" ]
-
-# List of files that should be passed to html_to_wrapper().
-recipes_web_component_files = [ "modules/recipes/module.ts" ]
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes/recipes_handler_proxy.ts b/chrome/browser/resources/new_tab_page/modules/recipes/recipes_handler_proxy.ts
deleted file mode 100644
index dd25c63c..0000000
--- a/chrome/browser/resources/new_tab_page/modules/recipes/recipes_handler_proxy.ts
+++ /dev/null
@@ -1,25 +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 type {RecipesHandlerRemote} from '../../recipes.mojom-webui.js';
-import {RecipesHandler} from '../../recipes.mojom-webui.js';
-
-/**
- * @fileoverview This file provides a class that exposes the Mojo handler
- * interface used for retrieving a shopping task for a task module.
- */
-
-let handler: RecipesHandlerRemote|null = null;
-
-export class RecipesHandlerProxy {
-  static getHandler(): RecipesHandlerRemote {
-    return handler || (handler = RecipesHandler.getRemote());
-  }
-
-  static setHandler(newHandler: RecipesHandlerRemote) {
-    handler = newHandler;
-  }
-
-  private constructor() {}
-}
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/calendar/calendar.gni b/chrome/browser/resources/new_tab_page/modules/v2/calendar/calendar.gni
new file mode 100644
index 0000000..fe431c9
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/v2/calendar/calendar.gni
@@ -0,0 +1,6 @@
+# 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.
+
+# List of files that should be passed to html_to_wrapper().
+calendar_v2_web_component_files = [ "modules/v2/calendar/module.ts" ]
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.html b/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.html
new file mode 100644
index 0000000..cb0c40a
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.html
@@ -0,0 +1,4 @@
+<ntp-module-header-v2
+    id="moduleHeaderElementV2"
+    header-text="[[i18n('modulesGoogleCalendarTitle')]]">
+</ntp-module-header-v2>
\ No newline at end of file
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.ts b/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.ts
new file mode 100644
index 0000000..baf4c12
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.ts
@@ -0,0 +1,41 @@
+// 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 '../../module_header.js';
+
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {I18nMixin} from '../../../i18n_setup.js';
+import {ModuleDescriptor} from '../../module_descriptor.js';
+
+import {getTemplate} from './module.html.js';
+
+/**
+ * The Calendar module, which serves as an inside look in to upcoming events on
+ * a user's Google Calendar or Microsoft Outlook.
+ */
+export class CalendarModuleElement extends I18nMixin
+(PolymerElement) {
+  static get is() {
+    return 'ntp-calendar-module';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {};
+  }
+}
+
+customElements.define(CalendarModuleElement.is, CalendarModuleElement);
+
+async function createCalendarElement(): Promise<CalendarModuleElement|null> {
+  return new Promise<CalendarModuleElement>(
+      (resolve) => resolve(new CalendarModuleElement()));
+}
+
+export const googleCalendarDescriptor: ModuleDescriptor = new ModuleDescriptor(
+    /*id*/ 'google_calendar', createCalendarElement);
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/file_suggestion/module.ts b/chrome/browser/resources/new_tab_page/modules/v2/file_suggestion/module.ts
index 928bf17..864c668 100644
--- a/chrome/browser/resources/new_tab_page/modules/v2/file_suggestion/module.ts
+++ b/chrome/browser/resources/new_tab_page/modules/v2/file_suggestion/module.ts
@@ -138,4 +138,4 @@
 }
 
 export const fileSuggestionDescriptor: ModuleDescriptor = new ModuleDescriptor(
-    /*id*/ 'file_suggestion', createFileElement);
+    /*id*/ 'drive', createFileElement);
diff --git a/chrome/browser/resources/settings/privacy_page/cookies_page.html b/chrome/browser/resources/settings/privacy_page/cookies_page.html
index 60891a1..ea5d007a 100644
--- a/chrome/browser/resources/settings/privacy_page/cookies_page.html
+++ b/chrome/browser/resources/settings/privacy_page/cookies_page.html
@@ -252,6 +252,13 @@
         hidden="[[!isIpProtectionAvailable_]]"
         icon="settings:location-disabled">
     </settings-toggle-button>
+    <settings-toggle-button
+        id="fingerprintingProtectionToggle"
+        pref="{{prefs.tracking_protection.fingerprinting_protection_enabled}}"
+        on-settings-boolean-control-change="onFingerprintingProtectionChanged_"
+        hidden="[[!isFingerprintingProtectionAvailable_]]"
+        icon="settings:fingerprint-off">
+    </settings-toggle-button>
     <settings-do-not-track-toggle id="doNotTrack" prefs="{{prefs}}">
     </settings-do-not-track-toggle>
     <cr-link-row id="site-data-trigger" class="hr"
diff --git a/chrome/browser/resources/settings/privacy_page/cookies_page.ts b/chrome/browser/resources/settings/privacy_page/cookies_page.ts
index 67cf4fa..08ad525 100644
--- a/chrome/browser/resources/settings/privacy_page/cookies_page.ts
+++ b/chrome/browser/resources/settings/privacy_page/cookies_page.ts
@@ -137,6 +137,12 @@
         value: () => loadTimeData.getBoolean('isIpProtectionV1Enabled'),
       },
 
+      isFingerprintingProtectionAvailable_: {
+        type: Boolean,
+        value: () =>
+            loadTimeData.getBoolean('isFingerprintingProtectionEnabled'),
+      },
+
       showTrackingProtectionRollbackNotice_: {
         type: Boolean,
         value: () => loadTimeData.getBoolean(
@@ -159,6 +165,7 @@
   private enableFirstPartySetsUI_: boolean;
   private is3pcdRedesignEnabled_: boolean;
   private isIpProtectionAvailable_: boolean;
+  private isFingerprintingProtectionAvailable_: boolean;
 
   private metricsBrowserProxy_: MetricsBrowserProxy =
       MetricsBrowserProxyImpl.getInstance();
@@ -236,6 +243,11 @@
     }
   }
 
+  private onFingerprintingProtectionChanged_() {
+    this.metricsBrowserProxy_.recordSettingsPageHistogram(
+        PrivacyElementInteractions.FINGERPRINTING_PROTECTION);
+  }
+
   private onIpProtectionChanged_() {
     this.metricsBrowserProxy_.recordSettingsPageHistogram(
         PrivacyElementInteractions.IP_PROTECTION);
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html
index ffda8d57..ad67b13a 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html
@@ -1402,7 +1402,7 @@
        </template>
       </template>
       <template is="dom-if" if="[[enableAutomaticFullscreenContentSetting_]]">
-        <template is="dom-if" route-path="/content/automaticFullscreen"
+        <template is="dom-if" route-path="/content/automaticFullScreen"
             no-search>
           <settings-subpage
               page-title="$i18n{siteSettingsCategoryAutomaticFullscreen}"
@@ -1411,9 +1411,6 @@
             <div class="content-settings-header secondary">
               $i18n{siteSettingsAutomaticFullscreenDescription}
             </div>
-            <div id="automaticFullscreenBlock" class="cr-row first secondary">
-              $i18n{siteSettingsAutomaticFullscreenBlock}
-            </div>
             <category-setting-exceptions read-only-list
                 category="[[contentSettingsTypesEnum_.AUTOMATIC_FULLSCREEN]]"
                 allow-header=
diff --git a/chrome/browser/resources/settings/route.ts b/chrome/browser/resources/settings/route.ts
index 94d1a5d3..162afbb3 100644
--- a/chrome/browser/resources/settings/route.ts
+++ b/chrome/browser/resources/settings/route.ts
@@ -148,7 +148,7 @@
 
   if (loadTimeData.getBoolean('enableAutomaticFullscreenContentSetting')) {
     r.SITE_SETTINGS_AUTOMATIC_FULLSCREEN =
-        r.SITE_SETTINGS.createChild('automaticFullscreen');
+        r.SITE_SETTINGS.createChild('automaticFullScreen');
   }
 }
 
diff --git a/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts b/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
index 19e21d2..689283ee 100644
--- a/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
+++ b/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
@@ -251,7 +251,6 @@
       id: Id.AUTOMATIC_FULLSCREEN,
       label: 'siteSettingsAutomaticFullscreen',
       icon: 'cr:fullscreen',
-      disabledLabel: 'siteSettingsAutomaticFullscreenBlock',
       shouldShow: () =>
           loadTimeData.getBoolean('enableAutomaticFullscreenContentSetting'),
     },
diff --git a/chrome/browser/resources/side_panel/read_anything/app.ts b/chrome/browser/resources/side_panel/read_anything/app.ts
index 64e9cc66..7ec3b686 100644
--- a/chrome/browser/resources/side_panel/read_anything/app.ts
+++ b/chrome/browser/resources/side_panel/read_anything/app.ts
@@ -66,7 +66,9 @@
 const defaultSelectionColor = 'var(--google-yellow-100)';
 const yellowThemeSelectionColor = 'var(--google-blue-100)';
 
-const previousReadHighlightClass = 'previous-read-highlight';
+export const previousReadHighlightClass = 'previous-read-highlight';
+export const currentReadHighlightClass = 'current-read-highlight';
+const parentOfHighlightClass = 'parent-of-highlight';
 
 const linkDataAttribute = 'link';
 
@@ -216,6 +218,12 @@
   // AXNodeIDs are unique, so this is a two way map where either DOM node or
   // AXNodeID can be used to access the other.
   private domNodeToAxNodeIdMap_: TwoWayMap<Node, number> = new TwoWayMap();
+  // Key: a DOM node that's already been read aloud
+  // Value: the index offset at which this node's text begins within its parent
+  // text. For reading aloud we sometimes split up nodes so the speech sounds
+  // more natural. When that text is then selected we need to pass the correct
+  // index down the pipeline, so we store that info here.
+  private highlightedNodeToOffsetInParent: Map<Node, number> = new Map();
   private imageNodeIdsToFetch_: Set<number> = new Set();
   private pendingImageRequest_?: PendingImageRequest;
 
@@ -282,6 +290,11 @@
 
   override connectedCallback() {
     super.connectedCallback();
+    // onConnected should always be called first in connectedCallback to ensure
+    // we're not blocking onConnected on anything else during WebUI setup.
+    if (chrome.readingMode) {
+      chrome.readingMode.onConnected();
+    }
 
     // Wait until the side panel is fully rendered before showing the side
     // panel. This follows Side Panel best practices and prevents loading
@@ -291,9 +304,6 @@
       setTimeout(() => chrome.readingMode.shouldShowUi(), 0);
     });
 
-    if (chrome.readingMode) {
-      chrome.readingMode.onConnected();
-    }
     this.synth.onvoiceschanged = () => {
       this.getVoices(/*refresh =*/ true);
     };
@@ -317,11 +327,23 @@
         chrome.readingMode.onCollapseSelection();
         return;
       }
-      const anchorNodeId = this.domNodeToAxNodeIdMap_.get(anchorNode);
-      const focusNodeId = this.domNodeToAxNodeIdMap_.get(focusNode);
+      let anchorNodeId = this.domNodeToAxNodeIdMap_.get(anchorNode);
+      let focusNodeId = this.domNodeToAxNodeIdMap_.get(focusNode);
+      let adjustedAnchorOffset = anchorOffset;
+      let adjustedFocusOffset = focusOffset;
+      // If the node was highlighted, then we need to find the parent node which
+      // we stored in the map, rather than the node itself
+      if (!anchorNodeId) {
+        anchorNodeId = this.getHighlightedAncestorId_(anchorNode);
+        adjustedAnchorOffset += this.getOffsetInAncestor(anchorNode);
+      }
+      if (!focusNodeId) {
+        focusNodeId = this.getHighlightedAncestorId_(focusNode);
+        adjustedFocusOffset += this.getOffsetInAncestor(focusNode);
+      }
       assert(anchorNodeId && focusNodeId, 'anchor or focus node is undefined');
       chrome.readingMode.onSelectionChange(
-          anchorNodeId, anchorOffset, focusNodeId, focusOffset);
+          anchorNodeId, adjustedAnchorOffset, focusNodeId, adjustedFocusOffset);
     };
 
     document.onscroll = () => {
@@ -337,6 +359,31 @@
     };
   }
 
+  private getOffsetInAncestor(node: Node): number {
+    if (this.highlightedNodeToOffsetInParent.has(node)) {
+      return this.highlightedNodeToOffsetInParent.get(node)!;
+    }
+
+    return 0;
+  }
+
+  private getHighlightedAncestorId_(node: Node): number|undefined {
+    if (!node.parentElement || !node.parentNode) {
+      return undefined;
+    }
+
+    let ancestor;
+    if (node.parentElement.className === parentOfHighlightClass) {
+      ancestor = node.parentNode;
+    } else if (
+        node.parentElement.parentElement?.className ===
+        parentOfHighlightClass) {
+      ancestor = node.parentNode.parentNode;
+    }
+
+    return ancestor ? this.domNodeToAxNodeIdMap_.get(ancestor) : undefined;
+  }
+
   private buildSubtree_(nodeId: number): Node {
     let htmlTag = chrome.readingMode.getHtmlTag(nodeId);
     const dataAttributes = new Map<string, string>();
@@ -891,7 +938,7 @@
       // updateContent, such as for links being toggled on or off via a Read
       // Aloud play / pause or via a preference change, rehighlight the nodes
       // after a pause.
-      if (!container.querySelector('.current-read-highlight')) {
+      if (!container.querySelector('.' + currentReadHighlightClass)) {
         // TODO(crbug.com/1474951): Investigate adding a mock voice in tests
         // to make this testable.
         this.highlightNodes(chrome.readingMode.getCurrentText());
@@ -1129,8 +1176,7 @@
         // If the start or end index is invalid, don't use this node.
         continue;
       }
-      const newElement: Node = this.highlightCurrentText_(start, end, element);
-      this.domNodeToAxNodeIdMap_.set(newElement, nodeId);
+      this.highlightCurrentText_(start, end, element as HTMLElement);
     }
   }
 
@@ -1157,16 +1203,16 @@
   //   <span class="current-read-highlight"> highlighted text </span>
   //   suffix text
   // </span>
-  // and returns the top-level span node
   private highlightCurrentText_(
-      toHighlightStart: number, toHighlightEnd: number,
-      currentNode: Node): Node {
+      highlightStart: number, highlightEnd: number,
+      currentNode: HTMLElement): void {
     const parentOfHighlight = document.createElement('span');
+    parentOfHighlight.className = parentOfHighlightClass;
 
     // First pull out any text within this node before the highlighted section.
     // Since it's already been highlighted, we fade it out.
     const highlightPrefix =
-        currentNode.textContent!.substring(0, toHighlightStart);
+        currentNode.textContent!.substring(0, highlightStart);
     if (highlightPrefix.length > 0) {
       const prefixNode = document.createElement('span');
       prefixNode.className = previousReadHighlightClass;
@@ -1177,29 +1223,29 @@
     // Then get the section of text to highlight and mark it for
     // highlighting.
     const readingHighlight = document.createElement('span');
-    readingHighlight.className = 'current-read-highlight';
-    readingHighlight.textContent =
-        currentNode.textContent!.substring(toHighlightStart, toHighlightEnd);
+    readingHighlight.className = currentReadHighlightClass;
+    const textNode = document.createTextNode(
+        currentNode.textContent!.substring(highlightStart, highlightEnd));
+    readingHighlight.appendChild(textNode);
+    this.highlightedNodeToOffsetInParent.set(textNode, highlightStart);
     parentOfHighlight.appendChild(readingHighlight);
 
     // Finally, append the rest of the text for this node that has yet to be
     // highlighted.
-    const highlightSuffix = currentNode.textContent!.substring(toHighlightEnd);
+    const highlightSuffix = currentNode.textContent!.substring(highlightEnd);
     if (highlightSuffix.length > 0) {
       const suffixNode = document.createTextNode(highlightSuffix);
+      this.highlightedNodeToOffsetInParent.set(suffixNode, highlightEnd);
       parentOfHighlight.appendChild(suffixNode);
     }
 
     // Replace the current node in the tree with the split up version of the
     // node.
     this.previousHighlight_.push(readingHighlight);
-    if (currentNode.parentNode) {
-      currentNode.parentNode.replaceChild(parentOfHighlight, currentNode);
-    }
+    this.replaceElement(currentNode, parentOfHighlight);
 
     // Automatically scroll the text so the highlight stays roughly centered.
     readingHighlight.scrollIntoViewIfNeeded();
-    return parentOfHighlight;
   }
 
   private onSpeechFinished() {
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html
index fa6778fd..63e12e4 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html
@@ -173,7 +173,7 @@
             on-keydown="onFontSelectKeyDown_"
             aria-label="$i18n{fontNameTitle}"
             title="$i18n{fontNameTitle}">
-      <template is="dom-repeat" items="[[fontOptions_]]">
+      <template is="dom-repeat" items="[[fontOptions_]]" initial-count="8">
         <option value="[[item]]">
           [[getFontItemLabel_(item, areFontsLoaded_)]]</option>
       </template>
@@ -201,7 +201,7 @@
 
   <hr class="separator" aria-hidden="true">
 
-  <template is="dom-repeat" items="[[textStyleToggles_]]">
+  <template is="dom-repeat" items="[[textStyleToggles_]]" initial-count="1">
     <cr-icon-button
     tabindex="-1"
     class="toolbar-button"
@@ -214,7 +214,7 @@
     </cr-icon-button>
   </template>
 
-  <template is="dom-repeat" items="[[textStyleOptions_]]">
+  <template is="dom-repeat" items="[[textStyleOptions_]]" initial-count="2">
     <cr-icon-button
       class="toolbar-button"
       id="[[item.id]]"
@@ -250,7 +250,7 @@
        -->
   <cr-action-menu id="moreOptionsMenu" on-keydown="onToolbarKeyDown_"
         hidden>
-    <template is="dom-repeat" items="[[moreOptionsButtons_]]">
+    <template is="dom-repeat" items="[[moreOptionsButtons_]]" inital-count="3">
       <cr-icon-button
         id="[[item.id]]"
         class="more-options-icon"
@@ -265,7 +265,8 @@
   <cr-lazy-render id="rateMenu">
     <template>
       <cr-action-menu>
-        <template is="dom-repeat" items="[[rateOptions_]]" index-as="index">
+        <template is="dom-repeat" items="[[rateOptions_]]" index-as="index"
+                  initial-count="8">
           <button class="dropdown-item" on-click="onRateClick_">
             <iron-icon class$="button-image check-mark
               check-mark-hidden-[[isRateItemSelected_(index)]]"
@@ -276,9 +277,9 @@
       </cr-action-menu>
     </template>
   </cr-lazy-render>
-  <cr-lazy-render id="fontSizeMenu" on-keydown="onFontSizeMenuKeyDown_">
+  <cr-lazy-render id="fontSizeMenu">
     <template>
-      <cr-action-menu>
+      <cr-action-menu on-keydown="onFontSizeMenuKeyDown_">
         <cr-icon-button
             class="font-size"
             id="font-size-decrease"
@@ -307,7 +308,8 @@
   <cr-lazy-render id="colorMenu">
     <template>
       <cr-action-menu>
-        <template is="dom-repeat" items="[[colorOptions_]]" index-as="index">
+        <template is="dom-repeat" items="[[colorOptions_]]" index-as="index"
+                  initial-count="5">
           <button class="dropdown-item" on-click="onColorClick_">
             <iron-icon class$="button-image check-mark
                               check-mark-hidden-[[isColorItemSelected_(index)]]"
@@ -323,7 +325,8 @@
     <template>
       <cr-action-menu>
         <template is="dom-repeat"
-                  items="[[lineSpacingOptions_]]" index-as="index">
+                  items="[[lineSpacingOptions_]]" index-as="index"
+                  initial-count="3">
           <button class="dropdown-item" on-click="onLineSpacingClick_">
             <iron-icon class$="button-image check-mark
                         check-mark-hidden-[[isLineSpacingItemSelected_(index)]]"
@@ -339,7 +342,8 @@
     <template>
       <cr-action-menu>
         <template is="dom-repeat"
-                  items="[[letterSpacingOptions_]]" index-as="index">
+                  items="[[letterSpacingOptions_]]" index-as="index"
+                  initial-count="3">
           <button class="dropdown-item" on-click="onLetterSpacingClick_">
             <iron-icon class$="button-image check-mark
                       check-mark-hidden-[[isLetterSpacingItemSelected_(index)]]"
@@ -354,7 +358,8 @@
   <cr-lazy-render id="fontMenu">
     <template>
       <cr-action-menu>
-        <template is="dom-repeat" items="[[fontOptions_]]" index-as="index">
+        <template is="dom-repeat" items="[[fontOptions_]]" index-as="index"
+                  initial-count="8">
           <button class="dropdown-item" on-click="onFontClick_"
               style$="font-family:[[item]]">
             <iron-icon
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
index 9991b63..17f3280 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
@@ -338,9 +338,13 @@
   // the parent element via one way data binding.
   private readonly hasContent: boolean;
 
+  constructor() {
+    super();
+    this.isReadAloudEnabled_ = chrome.readingMode.isReadAloudEnabled;
+  }
+
   override connectedCallback() {
     super.connectedCallback();
-    this.isReadAloudEnabled_ = chrome.readingMode.isReadAloudEnabled;
     if (this.isReadAloudEnabled_) {
       this.textStyleOptions_.push(
           {
@@ -578,22 +582,24 @@
     target.className += activeClass;
     this.closeMenus_();
 
-    const minY = target.getBoundingClientRect().bottom;
-    if (fullScreen) {
-      menuToOpen.showAt(target, {
-        minY: minY,
-        left: 0,
-        anchorAlignmentY: AnchorAlignment.AFTER_END,
-        noOffset: true,
-      });
-    } else {
-      menuToOpen.showAt(target, {
-        minY: minY,
-        anchorAlignmentX: AnchorAlignment.AFTER_START,
-        anchorAlignmentY: AnchorAlignment.AFTER_END,
-        noOffset: true,
-      });
-    }
+    requestAnimationFrame(() => {
+      const minY = target.getBoundingClientRect().bottom;
+      if (fullScreen) {
+        menuToOpen.showAt(target, {
+          minY: minY,
+          left: 0,
+          anchorAlignmentY: AnchorAlignment.AFTER_END,
+          noOffset: true,
+        });
+      } else {
+        menuToOpen.showAt(target, {
+          minY: minY,
+          anchorAlignmentX: AnchorAlignment.AFTER_START,
+          anchorAlignmentY: AnchorAlignment.AFTER_END,
+          noOffset: true,
+        });
+      }
+    });
   }
 
   private onHighlightClick_() {
@@ -817,9 +823,36 @@
     this.onKeyDown_(e, focusableElements);
   }
 
+  private getNewIndex_(e: KeyboardEvent, focusableElements: HTMLElement[]):
+      number {
+    let currentIndex = focusableElements.indexOf(e.target as HTMLElement);
+    const direction =
+        (e.key === 'ArrowRight' || e.key === 'ArrowDown') ? 1 : -1;
+    // If e.target wasn't found in focusable elements, and we're going
+    // backwards, adjust currentIndex so we move to the last focusable element
+    if (currentIndex === -1 && direction === -1) {
+      currentIndex = focusableElements.length;
+    }
+    // Move to the next focusable item in the menu, wrapping around
+    // if we've reached the end or beginning.
+    return (currentIndex + direction + focusableElements.length) %
+        focusableElements.length;
+  }
+
   private onFontSizeMenuKeyDown_(e: KeyboardEvent) {
-    this.onKeyDown_(
-        e, Array.from(this.$.fontSizeMenu.get().children) as HTMLElement[]);
+    // The font size selection menu is laid out horizontally, so users should be
+    // able to navigate it using either up and down arrows, or left and right
+    // arrows.
+    if (!['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(e.key)) {
+      return;
+    }
+    e.preventDefault();
+    const focusableElements =
+        Array.from(this.$.fontSizeMenu.get().children) as HTMLElement[];
+    const elementToFocus =
+        focusableElements[this.getNewIndex_(e, focusableElements)];
+    assert(elementToFocus, 'no element to focus');
+    elementToFocus.focus();
   }
 
   private onKeyDown_(e: KeyboardEvent, focusableElements: HTMLElement[]) {
@@ -828,12 +861,10 @@
     }
 
     e.preventDefault();
-    const currentIndex = focusableElements.indexOf(e.target as HTMLElement);
+    //  Move to the next focusable item in the toolbar, wrapping around
+    //  if we've reached the end or beginning.
+    let newIndex = this.getNewIndex_(e, focusableElements);
     const direction = e.key === 'ArrowRight' ? 1 : -1;
-    // Move to the next focusable item in the toolbar, wrapping around
-    // if we've reached the end or beginning.
-    let newIndex = (currentIndex + direction + focusableElements.length) %
-        focusableElements.length;
     // Skip focusing the button itself and go directly to the children. We still
     // need this button in the list of focusable elements because it can become
     // focused by tabbing while the menu is open and we want the arrow key
diff --git a/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.html b/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.html
index 499eebd..e884f3a1 100644
--- a/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.html
+++ b/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.html
@@ -79,6 +79,7 @@
   iron-icon="read-anything:voice-selection">
 </cr-icon-button>
 
+<!-- TODO(b/333785012): Add initial-counts to the dom-repeats here. -->
 <cr-lazy-render id="voiceSelectionMenu">
   <template>
     <cr-action-menu on-close="onClose_">
diff --git a/chrome/browser/resources/signin/profile_picker/profile_card_menu.html b/chrome/browser/resources/signin/profile_picker/profile_card_menu.html
index 3e64d45..a67e22a 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_card_menu.html
+++ b/chrome/browser/resources/signin/profile_picker/profile_card_menu.html
@@ -105,15 +105,13 @@
   }
 
   #removeConfirmationButton {
-    --active-shadow-action-rgb: var(--google-red-500-rgb);
-    --bg-action: var(--google-red-600);
-    --focus-shadow-color: rgba(var(--google-red-600-rgb), .4);
-    --hover-bg-action: rgba(var(--google-red-600-rgb), .9);
-    --hover-shadow-action-rgb: var(--google-red-500-rgb);
-    --hover-border-color: var(--google-red-100);
-    --hover-shadow-action-rgb: var(--google-red-500-rgb);
+    --cr-button-background-color: var(--google-red-600);
   }
 
+  #removeConfirmationButton:hover {
+    --cr-button-background-color: rgba(var(--google-red-600-rgb), .9);
+    --cr-button-border-color: var(--google-red-100);
+  }
 
   @media (prefers-color-scheme: dark) {
     #actionMenu button {
@@ -133,9 +131,12 @@
     }
 
     #removeConfirmationButton {
-      --bg-action: var(--google-red-300);
-      --focus-shadow-color: rgba(var(--google-red-300-rgb), .5);
-      --hover-bg-action: var(--bg-action)
+      --cr-button-background-color: var(--google-red-300);
+      --cr-button-text-color: var(--google-grey-900);
+    }
+
+    #removeConfirmationButton:hover {
+      --cr-button-background-color:
           linear-gradient(rgba(0, 0, 0, .08), rgba(0, 0, 0, .08));
     }
   }
diff --git a/chrome/browser/resources/webui_gallery/demos/buttons/buttons_demo.html b/chrome/browser/resources/webui_gallery/demos/buttons/buttons_demo.html
index 48c3220e9..a2b1363841 100644
--- a/chrome/browser/resources/webui_gallery/demos/buttons/buttons_demo.html
+++ b/chrome/browser/resources/webui_gallery/demos/buttons/buttons_demo.html
@@ -4,19 +4,6 @@
     padding: 0 12px;
     width: 45%;
   }
-
-  /* Some button styles are not available before Chrome Refresh 2023.
-   * cr-buttons that use these classes will fallback to the default
-   * button styles for non-[chrome-refresh-2023]. */
-  .tonal-button,
-  .floating-button {
-    display: none;
-  }
-
-  :host-context([chrome-refresh-2023]) .tonal-button,
-  :host-context([chrome-refresh-2023]) .floating-button {
-    display: inherit;
-  }
 </style>
 
 <h1>cr-button</h1>
diff --git a/chrome/browser/resources/welcome/BUILD.gn b/chrome/browser/resources/welcome/BUILD.gn
index cde3389..4e6b08a 100644
--- a/chrome/browser/resources/welcome/BUILD.gn
+++ b/chrome/browser/resources/welcome/BUILD.gn
@@ -91,7 +91,10 @@
     "shared/animations.css",
     "shared/chooser_shared.css",
     "shared/navi_colors.css",
+    "shared/navi_colors_lit.css",
+    "shared/onboarding_background.css",
     "shared/splash_pages_shared.css",
+    "shared/step_indicator.css",
   ]
 
   icons_html_files = [ "shared/icons.html" ]
@@ -104,10 +107,12 @@
     "//tools/typescript/definitions/metrics_private.d.ts",
   ]
   ts_deps = [
+    "//third_party/lit/v3_0:build_ts",
     "//third_party/polymer/v3_0:library",
     "//ui/webui/resources/cr_elements:build_ts",
     "//ui/webui/resources/js:build_ts",
   ]
 
+  html_to_wrapper_template = "detect"
   webui_context_type = "trusted"
 }
diff --git a/chrome/browser/resources/welcome/google_apps/nux_google_apps.ts b/chrome/browser/resources/welcome/google_apps/nux_google_apps.ts
index 1cd15ad2..cb9781ab 100644
--- a/chrome/browser/resources/welcome/google_apps/nux_google_apps.ts
+++ b/chrome/browser/resources/welcome/google_apps/nux_google_apps.ts
@@ -12,10 +12,10 @@
 import '../strings.m.js';
 
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {isRTL} from 'chrome://resources/js/util.js';
-import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
-import {afterNextRender, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {navigateToNextStep, NavigationMixin} from '../navigation_mixin.js';
 import type {BookmarkProxy} from '../shared/bookmark_proxy.js';
@@ -100,11 +100,6 @@
     this.bookmarkBarManager_ = BookmarkBarManager.getInstance();
   }
 
-  override connectedCallback() {
-    super.connectedCallback();
-    afterNextRender(this, () => IronA11yAnnouncer.requestAvailability());
-  }
-
   override onRouteEnter() {
     this.finalized_ = false;
     this.metricsManager_.recordPageInitialized();
@@ -153,8 +148,7 @@
   }
 
   private announceA11y_(text: string) {
-    this.dispatchEvent(new CustomEvent(
-        'iron-announce', {bubbles: true, composed: true, detail: {text}}));
+    getAnnouncerInstance().announce(text);
   }
 
   /**
diff --git a/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.html b/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.html
index 62c762ba..eb1c0184 100644
--- a/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.html
+++ b/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.html
@@ -119,15 +119,6 @@
     margin-top: 56px;
   }
 
-  #skipButton {
-    background-color: var(--cr-card-background-color)
-  }
-
-  #skipButton:hover {
-    background-image:
-        linear-gradient(var(--hover-bg-color), var(--hover-bg-color));
-  }
-
   /* Wallpaper Thumbnails */
   .art {
     background-image: url(../images/ntp_thumbnails/art.jpg);
diff --git a/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.ts b/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.ts
index 69ff6d7..038cd40d 100644
--- a/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.ts
+++ b/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.ts
@@ -13,6 +13,7 @@
 import '../strings.m.js';
 
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {isRTL} from 'chrome://resources/js/util.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -163,8 +164,7 @@
   }
 
   private announceA11y_(text: string) {
-    this.dispatchEvent(new CustomEvent(
-        'iron-announce', {bubbles: true, composed: true, detail: {text}}));
+    getAnnouncerInstance().announce(text);
   }
 
   private onBackgroundClick_(e: {model: {item: NtpBackgroundData}}) {
diff --git a/chrome/browser/resources/welcome/shared/navi_colors_lit.css b/chrome/browser/resources/welcome/shared/navi_colors_lit.css
new file mode 100644
index 0000000..ddb688d
--- /dev/null
+++ b/chrome/browser/resources/welcome/shared/navi_colors_lit.css
@@ -0,0 +1,11 @@
+/* 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style-lit
+ * #import=chrome://resources/cr_elements/cr_shared_vars.css.js
+ * #css_wrapper_metadata_end */
+
+/* Purposefully empty since this style is generated at build time from the
+ * equivalent Polymer version. */
diff --git a/chrome/browser/resources/welcome/shared/onboarding_background.css b/chrome/browser/resources/welcome/shared/onboarding_background.css
new file mode 100644
index 0000000..68b1e67
--- /dev/null
+++ b/chrome/browser/resources/welcome/shared/onboarding_background.css
@@ -0,0 +1,510 @@
+/* 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style-lit
+ * #scheme=relative
+ * #css_wrapper_metadata_end */
+
+:host {
+  --welcome-grey: rgb(218, 220, 224);
+  --welcome-blue: rgb(57, 130, 248);
+  --welcome-blue-tinted: rgb(138, 180, 248);
+  --welcome-green: rgb(52, 168, 83);
+  --welcome-green-tinted: rgb(129, 201, 149);
+  --welcome-red: rgb(219, 68, 55);
+  --welcome-yellow: rgb(251, 188, 4);
+  --welcome-yellow-tinted: rgb(253, 214, 99);
+  --welcome-animation-play-state: running;
+
+  display: flex;
+  justify-content: center;
+  max-width: 1100px;
+  position: relative;
+  width: 100%;
+}
+
+@media (prefers-color-scheme: dark) {
+  :host {
+    --welcome-grey: rgb(95, 99, 104);
+    --welcome-blue: rgb(57, 130, 248);
+    --welcome-blue-tinted: rgb(26, 115, 232);
+    --welcome-green: rgb(52, 168, 83);
+    --welcome-green-tinted: rgb(30, 142, 62);
+    --welcome-red: rgb(234, 66, 52);
+    --welcome-yellow: rgb(251, 188, 4);
+    --welcome-yellow-tinted: rgb(249, 171, 0);
+  }
+}
+
+:host([force-paused_]) {
+  --welcome-animation-play-state: paused;
+}
+
+#container {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  left: 50%;
+  position: absolute;
+  top: 50%;
+  transform: translate(-50%, -50%);
+}
+
+#canvas {
+  min-height: 878px;
+  min-width: 2728px;
+  position: relative;
+  transform: scale(0.5);
+}
+
+#logo {
+  background: url(../images/background_svgs/logo.svg);
+  background-size: contain;
+  border-radius: 50%;
+  height: 250px;
+  left: 50%;
+  top: 50%;
+  width: 250px;
+}
+
+.line-container {
+  border-radius: 8px;
+  height: 6px;
+  overflow: hidden;
+  position: absolute;
+  transform-origin: center left;
+}
+
+.line {
+  border-radius: 8px;
+  height: 100%;
+  overflow: hidden;
+  position: absolute;
+  transform-origin: center left;
+  width: 100%;
+}
+
+.line-fill {
+  background-color: var(--line-color);
+  border-radius: 8px;
+  height: 100%;
+  left: 0;
+  overflow: hidden;
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+
+#blue-line {
+  --line-color: var(--welcome-blue);
+  left: 1225px;
+  top: 278px;
+  transform: rotate(225deg);
+  width: 60px;
+}
+
+#green-line {
+  --line-color: var(--welcome-green);
+  left: 1170px;
+  top: 517px;
+  transform: rotate(-203deg);
+  width: 68px;
+}
+
+#red-line {
+  --line-color: var(--welcome-red);
+  left: 1574px;
+  top: 339px;
+  transform: rotate(-24deg);
+  width: 45px;
+}
+
+#grey-line {
+  --line-color: var(--welcome-grey);
+  left: 1403px;
+  top: 235px;
+  transform: rotate(280deg);
+  width: 68px;
+}
+
+#yellow-line {
+  --line-color: var(--welcome-yellow);
+  left: 1308px;
+  top: 655px;
+  transform: rotate(104deg);
+  width: 49px;
+}
+
+.shape {
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: contain;
+  height: 400px;
+  position: absolute;
+  transform: translate(-50%, -50%);
+  width: 400px;
+}
+
+.dotted-line {
+  -webkit-mask: url(../images/background_svgs/streamer_line.svg);
+  -webkit-mask-position: 3px 0;
+  -webkit-mask-size: 206px 12px;
+  animation: dotted-line 1s infinite linear;
+  animation-play-state: var(--welcome-animation-play-state);
+  background-color: var(--welcome-grey);
+  height: 12px;
+  left: 50%;
+  top: 50%;
+  transform-origin: center left;
+}
+
+@keyframes dotted-line {
+  to { -webkit-mask-position: 29px 0; }
+}
+
+#dotted-line-1 {
+  left: 1136px;
+  top: 378px;
+  transform: rotate(194deg);
+  width: 126px;
+}
+
+#dotted-line-2 {
+  left: 1493px;
+  top: 271px;
+  transform: rotate(-50deg);
+  width: 146px;
+}
+
+#dotted-line-3 {
+  left: 1545px;
+  top: 525px;
+  transform: rotate(25deg);
+  width: 120px;
+}
+
+#dotted-line-4 {
+  left: 1219px;
+  top: 626px;
+  transform: rotate(128deg);
+  width: 133px;
+}
+
+.circle {
+  background-color: var(--welcome-grey);
+  border-radius: 50%;
+  height: 64px;
+  width: 64px;
+}
+
+.connectagon-container {
+  --connectagon-shape-size: 64.77px;
+  display: block;
+  height: var(--connectagon-shape-size);
+  left: 50%;
+  position: absolute;
+  top: 50%;
+}
+
+.connectagon {
+  height: 100%;
+  transform-origin: 50% 50%;
+  width: 100%;
+}
+
+.connectagon .circle {
+  background-color: var(--connectagon-shape-color);
+  border-radius: 50%;
+  float: left;
+  height: var(--connectagon-shape-size);
+  position: relative;
+  width: var(--connectagon-shape-size);
+  z-index: 1;
+}
+
+.connectagon .square {
+  background-color: var(--connectagon-connector-color);
+  float: left;
+  height: var(--connectagon-shape-size);
+  margin-inline-start: calc(var(--connectagon-shape-size)/-2);
+  position: relative;
+  width: 59px;
+}
+
+.connectagon .hexagon {
+  -webkit-mask: url(../images/background_svgs/hexagon.svg) no-repeat top left;
+  background-color: var(--connectagon-shape-color);
+  float: left;
+  height: var(--connectagon-shape-size);
+  position: relative;
+  width: 72.78px;
+  z-index: 1;
+}
+
+:host-context([dir='rtl']) .connectagon :is(.circle, .square, .hexagon) {
+  float: right;
+}
+
+.connectagon .circle+.square+.hexagon,
+.connectagon .circle+.square+.circle {
+  margin-inline-start: -27.39px;
+}
+
+.connectagon .hexagon+.square+.hexagon {
+  margin-inline-start: -26.39px;
+}
+
+#yellow-connectagon {
+  --connectagon-shape-color: var(--welcome-yellow);
+  --connectagon-connector-color: var(--welcome-yellow-tinted);
+  animation: yellow-connectagon-translate 4s alternate infinite linear;
+  animation-play-state: var(--welcome-animation-play-state);
+  left: 549px;
+  top: 156px;
+  transform: translate(-50%, -50%) rotate(51deg);
+}
+
+@keyframes yellow-connectagon-translate {
+  to {
+    transform: translate(calc(-50% - 36px), calc(-50% - 62px)) rotate(51deg);
+  }
+}
+
+#yellow-connectagon .connectagon {
+  animation: yellow-connectagon-rotate 4s infinite linear;
+  animation-play-state: var(--welcome-animation-play-state);
+  transform: rotate(0deg);
+}
+
+@keyframes yellow-connectagon-rotate {
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+#green-triangle {
+  -webkit-mask: url(../images/background_svgs/triangle.svg);
+  animation: green-triangle 3s infinite cubic-bezier(.63,.03,.41,.98);
+  animation-play-state: var(--welcome-animation-play-state);
+  background-color: var(--welcome-green);
+  left: 1813px;
+  top: 0;
+  transform: translate(-50%, -50%) rotate(-10deg);
+  transform-origin: 200px 300px;
+}
+
+@keyframes green-triangle {
+  to {
+    transform: translate(-50%, -50%) rotate(350deg);
+  }
+}
+
+#blue-connectagon {
+  --connectagon-shape-color: var(--welcome-blue);
+  --connectagon-connector-color: var(--welcome-blue-tinted);
+  animation: blue-connectagon 4s alternate infinite linear;
+  animation-play-state: var(--welcome-animation-play-state);
+  left: 2157px;
+  top: 249px;
+  transform: translate(-50%, -50%);
+}
+
+@keyframes blue-connectagon {
+  50% {
+    transform: translate(calc(-50% + 10px), calc(-50% + 30px));
+  }
+  100% {
+    transform: translate(calc(-50%), calc(-50% + 60px));
+  }
+}
+
+#green-connectagon {
+  --connectagon-shape-color: var(--welcome-green);
+  --connectagon-connector-color: var(--welcome-green-tinted);
+  animation: green-connectagon-translate 6s alternate infinite linear;
+  animation-play-state: var(--welcome-animation-play-state);
+  left: 851px;
+  top: 766px;
+  transform: translate(-50%, -50%) rotate(139deg);
+}
+
+@keyframes green-connectagon-translate {
+  100% {
+    transform: translate(calc(-50% + 80px), calc(-50% + 20px)) rotate(139deg);
+  }
+}
+
+#green-connectagon .connectagon {
+  animation: green-connectagon-rotate 12s infinite linear;
+  animation-play-state: var(--welcome-animation-play-state);
+  transform: rotate(0deg);
+}
+
+@keyframes green-connectagon-rotate {
+  100% {
+    transform: rotate(-360deg);
+  }
+}
+
+#square {
+  -webkit-mask: url(../images/background_svgs/square.svg);
+  animation: square 4s infinite linear;
+  animation-play-state: var(--welcome-animation-play-state);
+  background-color: var(--welcome-yellow);
+  left: 1822px;
+  top: 716px;
+  transform: translate(-50%, -50%) rotate(29deg);
+}
+
+@keyframes square {
+  to {
+    transform: translate(-50%, -50%) rotate(389deg);
+  }
+}
+
+#grey-triangle {
+  -webkit-mask: url(../images/background_svgs/triangle.svg);
+  background-color: var(--welcome-grey);
+  left: 726px;
+  top: 414px;
+  transform: translate(-50%, -50%) rotate(-16deg);
+}
+
+#grey-circle-1 {
+  left: 380px;
+  top: 600px;
+  transform: translate(-50%, -50%);
+}
+
+#grey-circle-2 {
+  left: 1526px;
+  top: 739px;
+  transform: translate(-50%, -50%);
+}
+
+#grey-lozenge {
+  -webkit-mask: url(../images/background_svgs/lozenge.svg) no-repeat top left;
+  background-color: var(--welcome-grey);
+  left: 2351px;
+  top: 491px;
+  transform: translate(-50%, -50%) rotate(-32deg);
+}
+
+#password-field {
+  -webkit-mask: url(../images/background_svgs/password_field.svg);
+  background-color: var(--welcome-grey);
+  left: 860px;
+  top: 210px;
+  transform: translate(-50%, -50%) rotate(-11deg);
+}
+
+#password-field-input {
+  -webkit-mask: url(../images/background_svgs/password.svg);
+  -webkit-mask-position: top left;
+  -webkit-mask-size: 400px 400px;
+  animation: password-field-input 2s steps(1) infinite;
+  animation-play-state: var(--welcome-animation-play-state);
+  background-color: var(--welcome-green);
+  left: 604px;
+  top: 78px;
+  transform: rotate(-11deg);
+  transform-origin: top left;
+}
+
+@keyframes password-field-input {
+  0% { width: 150px; }
+  15% { width: 182px; }
+  30% { width: 218px; }
+  45% { width: 249px; }
+  60% { width: 400px; }
+  100% { width: 400px; }
+}
+
+#bookmarks-background {
+  -webkit-mask: url(../images/background_svgs/bookmarks_background.svg)
+      no-repeat top left;
+  background-color: var(--welcome-grey);
+  left: 937px;
+  top: 570px;
+  transform: translate(-50%, -50%) rotate(10deg);
+}
+
+#bookmarks-foreground {
+  -webkit-mask: url(../images/background_svgs/bookmarks_foreground.svg)
+      no-repeat top left;
+  background-color: var(--welcome-blue);
+  left: 937px;
+  top: 568px;
+  transform: translate(-50%, -50%) rotate(10deg);
+}
+
+#devices {
+  -webkit-mask: url(../images/background_svgs/devices.svg);
+  background-color: var(--welcome-grey);
+  left: 1885px;
+  top: 446px;
+  transform: translate(-50%, -50%) rotate(-9deg);
+}
+
+#devices-check {
+  -webkit-mask: url(../images/background_svgs/devices_check.svg);
+  background-color: var(--welcome-blue);
+  left: 1885px;
+  top: 446px;
+  transform: translate(-50%, -50%) rotate(-9deg);
+}
+
+#devices-circle {
+  /* Clip the top-left and bottom-right quadrants. */
+  clip-path: polygon(
+      48% 0, 100% 0, 100% 50%, 50% 50%, 52% 100%, 0 100%, 0 50%, 50% 47%);
+  left: 1832px;
+  top: 456px;
+  transform: translate(-50%, -50%) rotate(-9deg);
+}
+
+#devices-circle-image {
+  -webkit-mask: url(../images/background_svgs/streamer_circle.svg);
+  animation: devices-circle 11s infinite linear;
+  animation-play-state: var(--welcome-animation-play-state);
+  background-color: var(--welcome-green);
+  height: 100%;
+  width: 100%;
+}
+
+@keyframes devices-circle {
+  to { transform: rotate(-360deg); }
+}
+
+#playPause {
+  --cr-icon-button-fill-color: var(--cr-secondary-text-color);
+  --cr-icon-button-icon-size: 24px;
+  bottom: 100px;
+  left: 90%;
+  margin: 0;
+  opacity: 0;
+  position: absolute;
+  transition: opacity 300ms linear 500ms;
+}
+
+:host-context([dir=rtl]) #playPause {
+  left: auto;
+  right: 90%;
+}
+
+:host(:hover) #playPause,
+#playPause:focus-visible {
+  opacity: 1;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  :host {
+    --welcome-animation-play-state: paused;
+  }
+
+  #playPause {
+    display: none;
+  }
+}
diff --git a/chrome/browser/resources/welcome/shared/onboarding_background.html b/chrome/browser/resources/welcome/shared/onboarding_background.html
index e798748..33f8111 100644
--- a/chrome/browser/resources/welcome/shared/onboarding_background.html
+++ b/chrome/browser/resources/welcome/shared/onboarding_background.html
@@ -1,510 +1,6 @@
-<style>
-  :host {
-    --welcome-grey: rgb(218, 220, 224);
-    --welcome-blue: rgb(57, 130, 248);
-    --welcome-blue-tinted: rgb(138, 180, 248);
-    --welcome-green: rgb(52, 168, 83);
-    --welcome-green-tinted: rgb(129, 201, 149);
-    --welcome-red: rgb(219, 68, 55);
-    --welcome-yellow: rgb(251, 188, 4);
-    --welcome-yellow-tinted: rgb(253, 214, 99);
-    --welcome-animation-play-state: running;
-
-    display: flex;
-    justify-content: center;
-    max-width: 1100px;
-    position: relative;
-    width: 100%;
-  }
-
-  @media (prefers-color-scheme: dark) {
-    :host {
-      --welcome-grey: rgb(95, 99, 104);
-      --welcome-blue: rgb(57, 130, 248);
-      --welcome-blue-tinted: rgb(26, 115, 232);
-      --welcome-green: rgb(52, 168, 83);
-      --welcome-green-tinted: rgb(30, 142, 62);
-      --welcome-red: rgb(234, 66, 52);
-      --welcome-yellow: rgb(251, 188, 4);
-      --welcome-yellow-tinted: rgb(249, 171, 0);
-    }
-  }
-
-  :host([force-paused_]) {
-    --welcome-animation-play-state: paused;
-  }
-
-  #container {
-    align-items: center;
-    display: flex;
-    justify-content: center;
-    left: 50%;
-    position: absolute;
-    top: 50%;
-    transform: translate(-50%, -50%);
-  }
-
-  #canvas {
-    min-height: 878px;
-    min-width: 2728px;
-    position: relative;
-    transform: scale(0.5);
-  }
-
-  #logo {
-    background: url(../images/background_svgs/logo.svg);
-    background-size: contain;
-    border-radius: 50%;
-    height: 250px;
-    left: 50%;
-    top: 50%;
-    width: 250px;
-  }
-
-  .line-container {
-    border-radius: 8px;
-    height: 6px;
-    overflow: hidden;
-    position: absolute;
-    transform-origin: center left;
-  }
-
-  .line {
-    border-radius: 8px;
-    height: 100%;
-    overflow: hidden;
-    position: absolute;
-    transform-origin: center left;
-    width: 100%;
-  }
-
-  .line-fill {
-    background-color: var(--line-color);
-    border-radius: 8px;
-    height: 100%;
-    left: 0;
-    overflow: hidden;
-    position: absolute;
-    top: 0;
-    width: 100%;
-  }
-
-  #blue-line {
-    --line-color: var(--welcome-blue);
-    left: 1225px;
-    top: 278px;
-    transform: rotate(225deg);
-    width: 60px;
-  }
-
-  #green-line {
-    --line-color: var(--welcome-green);
-    left: 1170px;
-    top: 517px;
-    transform: rotate(-203deg);
-    width: 68px;
-  }
-
-  #red-line {
-    --line-color: var(--welcome-red);
-    left: 1574px;
-    top: 339px;
-    transform: rotate(-24deg);
-    width: 45px;
-  }
-
-  #grey-line {
-    --line-color: var(--welcome-grey);
-    left: 1403px;
-    top: 235px;
-    transform: rotate(280deg);
-    width: 68px;
-  }
-
-  #yellow-line {
-    --line-color: var(--welcome-yellow);
-    left: 1308px;
-    top: 655px;
-    transform: rotate(104deg);
-    width: 49px;
-  }
-
-  .shape {
-    background-position: center center;
-    background-repeat: no-repeat;
-    background-size: contain;
-    height: 400px;
-    position: absolute;
-    transform: translate(-50%, -50%);
-    width: 400px;
-  }
-
-  .dotted-line {
-    -webkit-mask: url(../images/background_svgs/streamer_line.svg);
-    -webkit-mask-position: 3px 0;
-    -webkit-mask-size: 206px 12px;
-    animation: dotted-line 1s infinite linear;
-    animation-play-state: var(--welcome-animation-play-state);
-    background-color: var(--welcome-grey);
-    height: 12px;
-    left: 50%;
-    top: 50%;
-    transform-origin: center left;
-  }
-
-  @keyframes dotted-line {
-    to { -webkit-mask-position: 29px 0; }
-  }
-
-  #dotted-line-1 {
-    left: 1136px;
-    top: 378px;
-    transform: rotate(194deg);
-    width: 126px;
-  }
-
-  #dotted-line-2 {
-    left: 1493px;
-    top: 271px;
-    transform: rotate(-50deg);
-    width: 146px;
-  }
-
-  #dotted-line-3 {
-    left: 1545px;
-    top: 525px;
-    transform: rotate(25deg);
-    width: 120px;
-  }
-
-  #dotted-line-4 {
-    left: 1219px;
-    top: 626px;
-    transform: rotate(128deg);
-    width: 133px;
-  }
-
-  .circle {
-    background-color: var(--welcome-grey);
-    border-radius: 50%;
-    height: 64px;
-    width: 64px;
-  }
-
-  .connectagon-container {
-    --connectagon-shape-size: 64.77px;
-    display: block;
-    height: var(--connectagon-shape-size);
-    left: 50%;
-    position: absolute;
-    top: 50%;
-  }
-
-  .connectagon {
-    height: 100%;
-    transform-origin: 50% 50%;
-    width: 100%;
-  }
-
-  .connectagon .circle {
-    background-color: var(--connectagon-shape-color);
-    border-radius: 50%;
-    float: left;
-    height: var(--connectagon-shape-size);
-    position: relative;
-    width: var(--connectagon-shape-size);
-    z-index: 1;
-  }
-
-  .connectagon .square {
-    background-color: var(--connectagon-connector-color);
-    float: left;
-    height: var(--connectagon-shape-size);
-    margin-inline-start: calc(var(--connectagon-shape-size)/-2);
-    position: relative;
-    width: 59px;
-  }
-
-  .connectagon .hexagon {
-    -webkit-mask: url(../images/background_svgs/hexagon.svg) no-repeat top left;
-    background-color: var(--connectagon-shape-color);
-    float: left;
-    height: var(--connectagon-shape-size);
-    position: relative;
-    width: 72.78px;
-    z-index: 1;
-  }
-
-  :host-context([dir='rtl']) .connectagon :is(.circle, .square, .hexagon) {
-    float: right;
-  }
-
-  .connectagon .circle+.square+.hexagon,
-  .connectagon .circle+.square+.circle {
-    margin-inline-start: -27.39px;
-  }
-
-  .connectagon .hexagon+.square+.hexagon {
-    margin-inline-start: -26.39px;
-  }
-
-  #yellow-connectagon {
-    --connectagon-shape-color: var(--welcome-yellow);
-    --connectagon-connector-color: var(--welcome-yellow-tinted);
-    animation: yellow-connectagon-translate 4s alternate infinite linear;
-    animation-play-state: var(--welcome-animation-play-state);
-    left: 549px;
-    top: 156px;
-    transform: translate(-50%, -50%) rotate(51deg);
-  }
-
-  @keyframes yellow-connectagon-translate {
-    to {
-      transform: translate(calc(-50% - 36px), calc(-50% - 62px)) rotate(51deg);
-    }
-  }
-
-  #yellow-connectagon .connectagon {
-    animation: yellow-connectagon-rotate 4s infinite linear;
-    animation-play-state: var(--welcome-animation-play-state);
-    transform: rotate(0deg);
-  }
-
-  @keyframes yellow-connectagon-rotate {
-    to {
-      transform: rotate(360deg);
-    }
-  }
-
-  #green-triangle {
-    -webkit-mask: url(../images/background_svgs/triangle.svg);
-    animation: green-triangle 3s infinite cubic-bezier(.63,.03,.41,.98);
-    animation-play-state: var(--welcome-animation-play-state);
-    background-color: var(--welcome-green);
-    left: 1813px;
-    top: 0;
-    transform: translate(-50%, -50%) rotate(-10deg);
-    transform-origin: 200px 300px;
-  }
-
-  @keyframes green-triangle {
-    to {
-      transform: translate(-50%, -50%) rotate(350deg);
-    }
-  }
-
-  #blue-connectagon {
-    --connectagon-shape-color: var(--welcome-blue);
-    --connectagon-connector-color: var(--welcome-blue-tinted);
-    animation: blue-connectagon 4s alternate infinite linear;
-    animation-play-state: var(--welcome-animation-play-state);
-    left: 2157px;
-    top: 249px;
-    transform: translate(-50%, -50%);
-  }
-
-  @keyframes blue-connectagon {
-    50% {
-      transform: translate(calc(-50% + 10px), calc(-50% + 30px));
-    }
-    100% {
-      transform: translate(calc(-50%), calc(-50% + 60px));
-    }
-  }
-
-  #green-connectagon {
-    --connectagon-shape-color: var(--welcome-green);
-    --connectagon-connector-color: var(--welcome-green-tinted);
-    animation: green-connectagon-translate 6s alternate infinite linear;
-    animation-play-state: var(--welcome-animation-play-state);
-    left: 851px;
-    top: 766px;
-    transform: translate(-50%, -50%) rotate(139deg);
-  }
-
-  @keyframes green-connectagon-translate {
-    100% {
-      transform: translate(calc(-50% + 80px), calc(-50% + 20px)) rotate(139deg);
-    }
-  }
-
-  #green-connectagon .connectagon {
-    animation: green-connectagon-rotate 12s infinite linear;
-    animation-play-state: var(--welcome-animation-play-state);
-    transform: rotate(0deg);
-  }
-
-  @keyframes green-connectagon-rotate {
-    100% {
-      transform: rotate(-360deg);
-    }
-  }
-
-  #square {
-    -webkit-mask: url(../images/background_svgs/square.svg);
-    animation: square 4s infinite linear;
-    animation-play-state: var(--welcome-animation-play-state);
-    background-color: var(--welcome-yellow);
-    left: 1822px;
-    top: 716px;
-    transform: translate(-50%, -50%) rotate(29deg);
-  }
-
-  @keyframes square {
-    to {
-      transform: translate(-50%, -50%) rotate(389deg);
-    }
-  }
-
-  #grey-triangle {
-    -webkit-mask: url(../images/background_svgs/triangle.svg);
-    background-color: var(--welcome-grey);
-    left: 726px;
-    top: 414px;
-    transform: translate(-50%, -50%) rotate(-16deg);
-  }
-
-  #grey-circle-1 {
-    left: 380px;
-    top: 600px;
-    transform: translate(-50%, -50%);
-  }
-
-  #grey-circle-2 {
-    left: 1526px;
-    top: 739px;
-    transform: translate(-50%, -50%);
-  }
-
-  #grey-lozenge {
-    -webkit-mask: url(../images/background_svgs/lozenge.svg) no-repeat top left;
-    background-color: var(--welcome-grey);
-    left: 2351px;
-    top: 491px;
-    transform: translate(-50%, -50%) rotate(-32deg);
-  }
-
-  #password-field {
-    -webkit-mask: url(../images/background_svgs/password_field.svg);
-    background-color: var(--welcome-grey);
-    left: 860px;
-    top: 210px;
-    transform: translate(-50%, -50%) rotate(-11deg);
-  }
-
-  #password-field-input {
-    -webkit-mask: url(../images/background_svgs/password.svg);
-    -webkit-mask-position: top left;
-    -webkit-mask-size: 400px 400px;
-    animation: password-field-input 2s steps(1) infinite;
-    animation-play-state: var(--welcome-animation-play-state);
-    background-color: var(--welcome-green);
-    left: 604px;
-    top: 78px;
-    transform: rotate(-11deg);
-    transform-origin: top left;
-  }
-
-  @keyframes password-field-input {
-    0% { width: 150px; }
-    15% { width: 182px; }
-    30% { width: 218px; }
-    45% { width: 249px; }
-    60% { width: 400px; }
-    100% { width: 400px; }
-  }
-
-  #bookmarks-background {
-    -webkit-mask: url(../images/background_svgs/bookmarks_background.svg)
-        no-repeat top left;
-    background-color: var(--welcome-grey);
-    left: 937px;
-    top: 570px;
-    transform: translate(-50%, -50%) rotate(10deg);
-  }
-
-  #bookmarks-foreground {
-    -webkit-mask: url(../images/background_svgs/bookmarks_foreground.svg)
-        no-repeat top left;
-    background-color: var(--welcome-blue);
-    left: 937px;
-    top: 568px;
-    transform: translate(-50%, -50%) rotate(10deg);
-  }
-
-  #devices {
-    -webkit-mask: url(../images/background_svgs/devices.svg);
-    background-color: var(--welcome-grey);
-    left: 1885px;
-    top: 446px;
-    transform: translate(-50%, -50%) rotate(-9deg);
-  }
-
-  #devices-check {
-    -webkit-mask: url(../images/background_svgs/devices_check.svg);
-    background-color: var(--welcome-blue);
-    left: 1885px;
-    top: 446px;
-    transform: translate(-50%, -50%) rotate(-9deg);
-  }
-
-  #devices-circle {
-    /* Clip the top-left and bottom-right quadrants. */
-    clip-path: polygon(
-        48% 0, 100% 0, 100% 50%, 50% 50%, 52% 100%, 0 100%, 0 50%, 50% 47%);
-    left: 1832px;
-    top: 456px;
-    transform: translate(-50%, -50%) rotate(-9deg);
-  }
-
-  #devices-circle-image {
-    -webkit-mask: url(../images/background_svgs/streamer_circle.svg);
-    animation: devices-circle 11s infinite linear;
-    animation-play-state: var(--welcome-animation-play-state);
-    background-color: var(--welcome-green);
-    height: 100%;
-    width: 100%;
-  }
-
-  @keyframes devices-circle {
-    to { transform: rotate(-360deg); }
-  }
-
-  #playPause {
-    --cr-icon-button-fill-color: var(--cr-secondary-text-color);
-    --cr-icon-button-icon-size: 24px;
-    bottom: 100px;
-    left: 90%;
-    margin: 0;
-    opacity: 0;
-    position: absolute;
-    transition: opacity 300ms linear 500ms;
-  }
-
-  :host-context([dir=rtl]) #playPause {
-    left: auto;
-    right: 90%;
-  }
-
-  :host(:hover) #playPause,
-  #playPause:focus-visible {
-    opacity: 1;
-  }
-
-  @media (prefers-reduced-motion: reduce) {
-    :host {
-      --welcome-animation-play-state: paused;
-    }
-
-    #playPause {
-      display: none;
-    }
-  }
-</style>
-
 <div id="container">
   <div id="canvas">
-    <div class="shape" id="logo" on-click="onLogoClick_"></div>
+    <div class="shape" id="logo" @click="${this.onLogoClick_}"></div>
 
     <!-- Lines surrounding logo. -->
     <div class="line-container" id="blue-line">
@@ -582,7 +78,7 @@
   </div>
 </div>
 
-<cr-icon-button id="playPause" iron-icon="[[getPlayPauseIcon_(forcePaused_)]]"
-    on-click="onPlayPauseClick_"
-    aria-label="[[getPlayPauseLabel_(forcePaused_)]]">
+<cr-icon-button id="playPause" iron-icon="${this.getPlayPauseIcon_()}"
+    @click="${this.onPlayPauseClick_}"
+    aria-label="${this.getPlayPauseLabel_()}">
 </cr-icon-button>
diff --git a/chrome/browser/resources/welcome/shared/onboarding_background.ts b/chrome/browser/resources/welcome/shared/onboarding_background.ts
index 6408353..2f31f45 100644
--- a/chrome/browser/resources/welcome/shared/onboarding_background.ts
+++ b/chrome/browser/resources/welcome/shared/onboarding_background.ts
@@ -13,9 +13,10 @@
 
 import {assert} from 'chrome://resources/js/assert.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
-import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
 
-import {getTemplate} from './onboarding_background.html.js';
+import {getCss} from './onboarding_background.css.js';
+import {getHtml} from './onboarding_background.html.js';
 
 export interface OnboardingBackgroundElement {
   $: {
@@ -23,20 +24,24 @@
   };
 }
 
-export class OnboardingBackgroundElement extends PolymerElement {
+export class OnboardingBackgroundElement extends CrLitElement {
   static get is() {
     return 'onboarding-background';
   }
 
-  static get template() {
-    return getTemplate();
+  static override get styles() {
+    return getCss();
   }
 
-  static get properties() {
+  override render() {
+    return getHtml.bind(this)();
+  }
+
+  static override get properties() {
     return {
       forcePaused_: {
         type: Boolean,
-        reflectToAttribute: true,
+        reflect: true,
       },
     };
   }
@@ -120,11 +125,11 @@
     this.loopAnimation_(lineTransformAnimation);
   }
 
-  private getPlayPauseIcon_(): string {
+  protected getPlayPauseIcon_(): string {
     return this.forcePaused_ ? 'welcome:play' : 'welcome:pause';
   }
 
-  private getPlayPauseLabel_(): string {
+  protected getPlayPauseLabel_(): string {
     return loadTimeData.getString(
         this.forcePaused_ ? 'landingPlayAnimations' : 'landingPauseAnimations');
   }
@@ -139,7 +144,7 @@
     };
   }
 
-  private onLogoClick_() {
+  protected onLogoClick_() {
     this.$.logo.animate(
         {
           transform: [
@@ -153,7 +158,7 @@
         });
   }
 
-  private onPlayPauseClick_() {
+  protected onPlayPauseClick_() {
     if (this.forcePaused_) {
       this.play();
     } else {
diff --git a/chrome/browser/resources/welcome/shared/step_indicator.css b/chrome/browser/resources/welcome/shared/step_indicator.css
new file mode 100644
index 0000000..03098537
--- /dev/null
+++ b/chrome/browser/resources/welcome/shared/step_indicator.css
@@ -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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style-lit
+ * #import=./navi_colors_lit.css.js
+ * #scheme=relative
+ * #include=navi-colors-lit
+ * #css_wrapper_metadata_end */
+
+:host {
+  align-items: center;
+  display: flex;
+}
+
+span {
+  background: var(--navi-step-indicator-color);
+  border-radius: 50%;
+  display: inline-block;
+  height: 8px;
+  margin: 0 4px;
+  width: 8px;
+}
+
+span.active {
+  background: var(--navi-step-indicator-active-color);
+}
+
+.screen-reader-only {
+  clip: rect(0, 0, 0, 0);
+  position: fixed;
+}
diff --git a/chrome/browser/resources/welcome/shared/step_indicator.html b/chrome/browser/resources/welcome/shared/step_indicator.html
index cd2f66e..2fb69ae 100644
--- a/chrome/browser/resources/welcome/shared/step_indicator.html
+++ b/chrome/browser/resources/welcome/shared/step_indicator.html
@@ -1,30 +1,7 @@
-<style include="navi-colors">
-  :host {
-    align-items: center;
-    display: flex;
-  }
-
-  span {
-    background: var(--navi-step-indicator-color);
-    border-radius: 50%;
-    display: inline-block;
-    height: 8px;
-    margin: 0 4px;
-    width: 8px;
-  }
-
-  span.active {
-    background: var(--navi-step-indicator-active-color);
-  }
-
-  .screen-reader-only {
-    clip: rect(0, 0, 0, 0);
-    position: fixed;
-  }
-</style>
-<template is="dom-repeat" items="[[dots_]]">
-  <span class$="[[getActiveClass_(index, model.active)]]"></span>
-</template>
-<div class="screen-reader-only">
-  [[computeLabel_(model.active, model.total)]]
-</div>
+<!-- #html_wrapper_imports_start
+import {nothing} from '//resources/lit/v3_0/lit.rollup.js';
+#html_wrapper_imports_end -->
+${this.dots_.map((_item, index) => html`
+  <span class="${this.getActiveClass_(index) || nothing}"></span>
+`)}
+<div class="screen-reader-only">${this.getLabel_()}</div>
diff --git a/chrome/browser/resources/welcome/shared/step_indicator.ts b/chrome/browser/resources/welcome/shared/step_indicator.ts
index c486e5c3..0cea340 100644
--- a/chrome/browser/resources/welcome/shared/step_indicator.ts
+++ b/chrome/browser/resources/welcome/shared/step_indicator.ts
@@ -6,52 +6,62 @@
  * @fileoverview This element contains a set of SVGs that together acts as an
  * animated and responsive background for any page that contains it.
  */
-import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
-import './navi_colors.css.js';
 
-import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
-import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {I18nMixinLit} from 'chrome://resources/cr_elements/i18n_mixin_lit.js';
+import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
+import type {PropertyValues} from 'chrome://resources/lit/v3_0/lit.rollup.js';
 
 import type {StepIndicatorModel} from './nux_types.js';
-import {getTemplate} from './step_indicator.html.js';
+import {getCss} from './step_indicator.css.js';
+import {getHtml} from './step_indicator.html.js';
 
-const StepIndicatorElementBase = I18nMixin(PolymerElement);
+const StepIndicatorElementBase = I18nMixinLit(CrLitElement);
 
-/** @polymer */
 export class StepIndicatorElement extends StepIndicatorElementBase {
   static get is() {
     return 'step-indicator';
   }
 
-  static get template() {
-    return getTemplate();
+  static override get styles() {
+    return getCss();
   }
 
-  static get properties() {
-    return {
-      model: Object,
+  override render() {
+    return getHtml.bind(this)();
+  }
 
-      dots_: {
-        type: Array,
-        computed: 'computeDots_(model.total)',
-      },
+  static override get properties() {
+    return {
+      model: {type: Object},
+      dots_: {type: Array},
     };
   }
 
-  model?: StepIndicatorModel;
-  private dots_?: undefined[];
+  model: StepIndicatorModel = {active: 0, total: 0};
+  protected dots_: number[] = [];
 
-  private computeLabel_(active: number, total: number): string {
-    return this.i18n('stepsLabel', active + 1, total);
+  override willUpdate(changedProperties: PropertyValues<this>) {
+    super.willUpdate(changedProperties);
+
+    if (changedProperties.has('model')) {
+      this.dots_ = this.computeDots_();
+    }
   }
 
-  private computeDots_(): undefined[] {
+  protected getLabel_(): string {
+    return this.i18n('stepsLabel', this.model.active + 1, this.model.total);
+  }
+
+  private computeDots_(): number[] {
     // If total is 1, show nothing.
-    return new Array(this.model!.total > 1 ? this.model!.total : 0);
+    const array: number[] = new Array(this.model.total > 1 ? this.model.total : 0);
+    array.fill(0);
+    return array;
   }
 
-  private getActiveClass_(index: number): string {
-    return index === this.model!.active ? 'active' : '';
+  protected getActiveClass_(index: number): string {
+    return index === this.model.active ? 'active' : '';
   }
 }
+
 customElements.define(StepIndicatorElement.is, StepIndicatorElement);
diff --git a/chrome/browser/safe_browsing/chrome_ping_manager_factory.cc b/chrome/browser/safe_browsing/chrome_ping_manager_factory.cc
index 1a1bfdc..03bc86f 100644
--- a/chrome/browser/safe_browsing/chrome_ping_manager_factory.cc
+++ b/chrome/browser/safe_browsing/chrome_ping_manager_factory.cc
@@ -74,7 +74,7 @@
       content::GetUIThreadTaskRunner({}),
       base::BindRepeating(&safe_browsing::GetUserPopulationForProfile, profile),
       base::BindRepeating(&safe_browsing::GetPageLoadTokenForURL, profile),
-      std::move(hats_delegate));
+      std::move(hats_delegate), /*persister_root_path=*/profile->GetPath());
 }
 
 // static
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.cc b/chrome/browser/safe_browsing/safe_browsing_service.cc
index 0f5bdade..4ba282c 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service.cc
@@ -553,7 +553,7 @@
       content::DownloadItemUtils::GetBrowserContext(download));
   return ChromePingManagerFactory::GetForBrowserContext(profile)
              ->PersistThreatDetailsAndReportOnNextStartup(std::move(report)) ==
-         PingManager::ReportThreatDetailsResult::SUCCESS;
+         PingManager::PersistThreatDetailsResult::kPersistTaskPosted;
 }
 
 bool SafeBrowsingService::SendPhishyInteractionsReport(
diff --git a/chrome/browser/sessions/session_restore.cc b/chrome/browser/sessions/session_restore.cc
index 88512a1..ac29026 100644
--- a/chrome/browser/sessions/session_restore.cc
+++ b/chrome/browser/sessions/session_restore.cc
@@ -940,10 +940,8 @@
 
     TabGroupModel* group_model = browser->tab_strip_model()->group_model();
     tab_groups::SavedTabGroupKeyedService* const saved_tab_group_keyed_service =
-        base::FeatureList::IsEnabled(features::kTabGroupsSave)
-            ? tab_groups::SavedTabGroupServiceFactory::GetForProfile(
-                  browser->profile())
-            : nullptr;
+        tab_groups::SavedTabGroupServiceFactory::GetForProfile(
+            browser->profile());
 
     for (const std::unique_ptr<sessions::SessionTabGroup>& session_tab_group :
          tab_groups) {
diff --git a/chrome/browser/sessions/session_service_base.cc b/chrome/browser/sessions/session_service_base.cc
index f6fa494..ef5cb461 100644
--- a/chrome/browser/sessions/session_service_base.cc
+++ b/chrome/browser/sessions/session_service_base.cc
@@ -630,10 +630,8 @@
     TabGroupModel* group_model = tab_strip->group_model();
     const tab_groups::SavedTabGroupKeyedService* const
         saved_tab_group_keyed_service =
-            base::FeatureList::IsEnabled(features::kTabGroupsSave)
-                ? tab_groups::SavedTabGroupServiceFactory::GetForProfile(
-                      browser->profile())
-                : nullptr;
+            tab_groups::SavedTabGroupServiceFactory::GetForProfile(
+                browser->profile());
 
     for (const tab_groups::TabGroupId& group_id :
          group_model->ListTabGroups()) {
diff --git a/chrome/browser/sessions/tab_restore_browsertest.cc b/chrome/browser/sessions/tab_restore_browsertest.cc
index d8d223c..7036e0d 100644
--- a/chrome/browser/sessions/tab_restore_browsertest.cc
+++ b/chrome/browser/sessions/tab_restore_browsertest.cc
@@ -2008,8 +2008,7 @@
 class TabRestoreSavedGroupsTest : public TabRestoreTest {
  public:
   TabRestoreSavedGroupsTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {features::kTabGroupsSave, tab_groups::kTabGroupsSaveV2}, {});
+    scoped_feature_list_.InitWithFeatures({tab_groups::kTabGroupsSaveV2}, {});
   }
 
   // Adds |how_many| tabs to the given browser, all navigated to the youtube.com
diff --git a/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc b/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
index 616a2b5..6a439028 100644
--- a/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
+++ b/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
@@ -1547,11 +1547,18 @@
 
 // This test loads a PDF inside an OOPIF and then verifies that context menu
 // shows up at the correct position.
-// TODO(1423184,327338993): Fix flaky test.
-// defined(ADDRESS_SANITIZER)
+// Flaky on win and linux asan. See https://crbug.com/1423184
+#if (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)) && defined(ADDRESS_SANITIZER)
+#define MAYBE_ContextMenuPositionForEmbeddedPDFInCrossOriginFrame \
+  DISABLED_ContextMenuPositionForEmbeddedPDFInCrossOriginFrame
+#else
+#define MAYBE_ContextMenuPositionForEmbeddedPDFInCrossOriginFrame \
+  ContextMenuPositionForEmbeddedPDFInCrossOriginFrame
+#endif  // (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)) &&
+        // defined(ADDRESS_SANITIZER)
 IN_PROC_BROWSER_TEST_P(
     SitePerProcessInteractivePDFTest,
-    DISABLED_ContextMenuPositionForEmbeddedPDFInCrossOriginFrame) {
+    MAYBE_ContextMenuPositionForEmbeddedPDFInCrossOriginFrame) {
   // Navigate to a page with an <iframe>.
   GURL main_url(embedded_test_server()->GetURL("a.com", "/iframe.html"));
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
diff --git a/chrome/browser/sync/chrome_sync_client.cc b/chrome/browser/sync/chrome_sync_client.cc
index ef28c14..8ee5d06b 100644
--- a/chrome/browser/sync/chrome_sync_client.cc
+++ b/chrome/browser/sync/chrome_sync_client.cc
@@ -464,8 +464,7 @@
     bool enable_tab_group_sync = false;
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || \
     BUILDFLAG(IS_WIN)
-    enable_tab_group_sync =
-        base::FeatureList::IsEnabled(features::kTabGroupsSave);
+    enable_tab_group_sync = true;
 #elif BUILDFLAG(IS_ANDROID)
     enable_tab_group_sync =
         base::FeatureList::IsEnabled(tab_groups::kTabGroupSyncAndroid);
@@ -640,9 +639,10 @@
     case syncer::SAVED_TAB_GROUP: {
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || \
     BUILDFLAG(IS_WIN)
-      DCHECK(base::FeatureList::IsEnabled(features::kTabGroupsSave));
-      return tab_groups::SavedTabGroupServiceFactory::GetForProfile(profile_)
-          ->bridge()
+      auto* keyed_service =
+          tab_groups::SavedTabGroupServiceFactory::GetForProfile(profile_);
+      CHECK(keyed_service);
+      return keyed_service->bridge()
           ->change_processor()
           ->GetControllerDelegate();
 #elif BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/sync/sync_service_factory_unittest.cc b/chrome/browser/sync/sync_service_factory_unittest.cc
index 701ce48..8c51e7f7 100644
--- a/chrome/browser/sync/sync_service_factory_unittest.cc
+++ b/chrome/browser/sync/sync_service_factory_unittest.cc
@@ -110,9 +110,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || \
     BUILDFLAG(IS_WIN)
-    if (base::FeatureList::IsEnabled(features::kTabGroupsSave)) {
-      datatypes.Put(syncer::SAVED_TAB_GROUP);
-    }
+    datatypes.Put(syncer::SAVED_TAB_GROUP);
 #elif BUILDFLAG(IS_ANDROID)
     if (base::FeatureList::IsEnabled(tab_groups::kTabGroupSyncAndroid)) {
       datatypes.Put(syncer::SAVED_TAB_GROUP);
diff --git a/chrome/browser/sync/test/integration/local_sync_test.cc b/chrome/browser/sync/test/integration/local_sync_test.cc
index cebfffd..0c2c334e 100644
--- a/chrome/browser/sync/test/integration/local_sync_test.cc
+++ b/chrome/browser/sync/test/integration/local_sync_test.cc
@@ -107,6 +107,7 @@
       syncer::AUTOFILL_WALLET_METADATA,
       syncer::THEMES,
       syncer::EXTENSIONS,
+      syncer::SAVED_TAB_GROUP,
       syncer::SEARCH_ENGINES,
       syncer::SESSIONS,
       syncer::APPS,
@@ -118,10 +119,6 @@
       syncer::WEB_APPS,
       syncer::NIGORI};
 
-  if (base::FeatureList::IsEnabled(features::kTabGroupsSave)) {
-    expected_active_data_types.Put(syncer::SAVED_TAB_GROUP);
-  }
-
   if (base::FeatureList::IsEnabled(power_bookmarks::kPowerBookmarkBackend)) {
     expected_active_data_types.Put(syncer::POWER_BOOKMARK);
   }
diff --git a/chrome/browser/sync/test/integration/single_client_saved_tab_groups_sync_test.cc b/chrome/browser/sync/test/integration/single_client_saved_tab_groups_sync_test.cc
index 35acfc1..e4db48f 100644
--- a/chrome/browser/sync/test/integration/single_client_saved_tab_groups_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_saved_tab_groups_sync_test.cc
@@ -25,9 +25,7 @@
 
 class SingleClientSavedTabGroupsSyncTest : public SyncTest {
  public:
-  SingleClientSavedTabGroupsSyncTest() : SyncTest(SINGLE_CLIENT) {
-    features_.InitAndEnableFeature(features::kTabGroupsSave);
-  }
+  SingleClientSavedTabGroupsSyncTest() : SyncTest(SINGLE_CLIENT) {}
   ~SingleClientSavedTabGroupsSyncTest() override = default;
   SingleClientSavedTabGroupsSyncTest(
       const SingleClientSavedTabGroupsSyncTest&) = delete;
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabStateAttributes.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabStateAttributes.java
index 641f5980..adc06bf2 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabStateAttributes.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabStateAttributes.java
@@ -32,10 +32,13 @@
     private static final Class<TabStateAttributes> USER_DATA_KEY = TabStateAttributes.class;
     @VisibleForTesting static final long DEFAULT_LOW_PRIORITY_SAVE_DELAY_MS = 30 * 1000L;
 
-    /** Defines the dirtiness states of the tab attributes. */
+    /**
+     * Defines the dirtiness states of the tab attributes. Numerical values should be setup such
+     * that higher values are more dirty.
+     */
     @IntDef({DirtinessState.CLEAN, DirtinessState.UNTIDY, DirtinessState.DIRTY})
     @Retention(RetentionPolicy.SOURCE)
-    public static @interface DirtinessState {
+    public @interface DirtinessState {
         /** The state of the tab has no meaningful changes. */
         int CLEAN = 0;
 
@@ -55,10 +58,20 @@
     private WebContentsObserver mWebContentsObserver;
     private boolean mPendingLowPrioritySave;
 
+    /**
+     * When this number is greater than zero, all dirty observations are currently being suppressed.
+     * Using an int instead of boolean to support reentrancy.
+     */
+    private int mNumberOpenBatchEdits;
+
+    /** The most dirty end state transition that's been had during the current changes, or null. */
+    private @Nullable Integer mPendingDirty;
+
     /** Allows observing changes for Tab state dirtiness updates. */
     public interface Observer {
         /**
          * Triggered when the tab state dirtiness has changed.
+         *
          * @param tab The tab whose state has changed.
          * @param dirtiness Tne state of dirtiness for the tab state.
          */
@@ -253,8 +266,12 @@
             mDirtinessState = dirtiness;
         }
 
-        for (Observer observer : mObservers) {
-            observer.onTabStateDirtinessChanged(mTab, mDirtinessState);
+        if (mNumberOpenBatchEdits > 0) {
+            updatePendingDirty(mDirtinessState);
+        } else {
+            for (Observer observer : mObservers) {
+                observer.onTabStateDirtinessChanged(mTab, mDirtinessState);
+            }
         }
     }
 
@@ -285,6 +302,34 @@
         mObservers.removeObserver(obs);
     }
 
+    /**
+     * Temporarily suppress dirty observations while multiple changes are made to this tab. This can
+     * be helpful to coalesce multiple changes into a single write to persistent storage. If you
+     * call this method you must call {@link #endBatchEdit()} as soon as changes are done being
+     * made, otherwise this tab will be left in a broken state.
+     */
+    public void beginBatchEdit() {
+        mNumberOpenBatchEdits++;
+    }
+
+    public void endBatchEdit() {
+        mNumberOpenBatchEdits--;
+        assert mNumberOpenBatchEdits >= 0;
+        if (mNumberOpenBatchEdits == 0 && mPendingDirty != null) {
+            // Reset mPendingDirty just in case we need to support reentrancy from observers.
+            int pendingDirtyCopy = mPendingDirty;
+            mPendingDirty = null;
+            for (Observer observer : mObservers) {
+                observer.onTabStateDirtinessChanged(mTab, pendingDirtyCopy);
+            }
+        }
+    }
+
+    private void updatePendingDirty(@DirtinessState int newDirty) {
+        mPendingDirty =
+                mPendingDirty == null ? newDirty : Math.max(mPendingDirty.intValue(), newDirty);
+    }
+
     /** Allows overriding the current value for tests. */
     public void setStateForTesting(@DirtinessState int dirtiness) {
         var oldValue = mDirtinessState;
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabStateAttributesTest.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabStateAttributesTest.java
index a6639de3..8315eb5 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabStateAttributesTest.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabStateAttributesTest.java
@@ -4,7 +4,10 @@
 
 package org.chromium.chrome.browser.tab;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 
 import android.os.Handler;
@@ -575,4 +578,71 @@
                 TabStateAttributes.DirtinessState.UNTIDY,
                 TabStateAttributes.from(mTab).getDirtinessState());
     }
+
+    @Test
+    public void testBatchEdit() {
+        TabStateAttributes.createForTab(mTab, TabCreationState.FROZEN_ON_RESTORE);
+        TabStateAttributes.from(mTab).addObserver(mAttributesObserver);
+
+        TabStateAttributes.from(mTab).beginBatchEdit();
+        mTab.setRootId(1);
+        mTab.setTabGroupId(Token.createRandom());
+        mTab.setRootId(2);
+        mTab.setTabGroupId(null);
+        Mockito.verify(mAttributesObserver, never()).onTabStateDirtinessChanged(eq(mTab), anyInt());
+        TabStateAttributes.from(mTab).endBatchEdit();
+        Mockito.verify(mAttributesObserver)
+                .onTabStateDirtinessChanged(mTab, TabStateAttributes.DirtinessState.DIRTY);
+        Mockito.reset(mAttributesObserver);
+
+        TabStateAttributes.from(mTab).beginBatchEdit();
+        TabStateAttributes.from(mTab).updateIsDirty(TabStateAttributes.DirtinessState.CLEAN);
+        Mockito.verify(mAttributesObserver, never()).onTabStateDirtinessChanged(eq(mTab), anyInt());
+        TabStateAttributes.from(mTab).endBatchEdit();
+        Mockito.verify(mAttributesObserver)
+                .onTabStateDirtinessChanged(mTab, TabStateAttributes.DirtinessState.CLEAN);
+        Mockito.reset(mAttributesObserver);
+
+        TabStateAttributes.from(mTab).beginBatchEdit();
+        TabStateAttributes.from(mTab).updateIsDirty(TabStateAttributes.DirtinessState.UNTIDY);
+        TabStateAttributes.from(mTab).updateIsDirty(TabStateAttributes.DirtinessState.CLEAN);
+        Mockito.verify(mAttributesObserver, never()).onTabStateDirtinessChanged(eq(mTab), anyInt());
+        TabStateAttributes.from(mTab).endBatchEdit();
+        Mockito.verify(mAttributesObserver)
+                .onTabStateDirtinessChanged(mTab, TabStateAttributes.DirtinessState.UNTIDY);
+        Mockito.reset(mAttributesObserver);
+
+        TabStateAttributes.from(mTab).beginBatchEdit();
+        TabStateAttributes.from(mTab).updateIsDirty(TabStateAttributes.DirtinessState.UNTIDY);
+        TabStateAttributes.from(mTab).updateIsDirty(TabStateAttributes.DirtinessState.CLEAN);
+        TabStateAttributes.from(mTab).updateIsDirty(TabStateAttributes.DirtinessState.DIRTY);
+        TabStateAttributes.from(mTab).updateIsDirty(TabStateAttributes.DirtinessState.CLEAN);
+        Mockito.verify(mAttributesObserver, never()).onTabStateDirtinessChanged(eq(mTab), anyInt());
+        TabStateAttributes.from(mTab).endBatchEdit();
+        Mockito.verify(mAttributesObserver)
+                .onTabStateDirtinessChanged(mTab, TabStateAttributes.DirtinessState.DIRTY);
+        Mockito.reset(mAttributesObserver);
+    }
+
+    @Test
+    public void testNestedBatchEdit() {
+        TabStateAttributes.createForTab(mTab, TabCreationState.FROZEN_ON_RESTORE);
+        TabStateAttributes.from(mTab).addObserver(mAttributesObserver);
+
+        TabStateAttributes.from(mTab).beginBatchEdit();
+        TabStateAttributes.from(mTab).updateIsDirty(TabStateAttributes.DirtinessState.UNTIDY);
+        Mockito.verify(mAttributesObserver, never()).onTabStateDirtinessChanged(eq(mTab), anyInt());
+
+        TabStateAttributes.from(mTab).beginBatchEdit();
+        TabStateAttributes.from(mTab).updateIsDirty(TabStateAttributes.DirtinessState.DIRTY);
+        Mockito.verify(mAttributesObserver, never()).onTabStateDirtinessChanged(eq(mTab), anyInt());
+
+        TabStateAttributes.from(mTab).endBatchEdit();
+        Mockito.verify(mAttributesObserver, never()).onTabStateDirtinessChanged(eq(mTab), anyInt());
+
+        TabStateAttributes.from(mTab).endBatchEdit();
+        Mockito.verify(mAttributesObserver)
+                .onTabStateDirtinessChanged(mTab, TabStateAttributes.DirtinessState.DIRTY);
+        Mockito.verifyNoMoreInteractions(mAttributesObserver);
+    }
 }
diff --git a/chrome/browser/tab_group/BUILD.gn b/chrome/browser/tab_group/BUILD.gn
index 81c35ca8..a1959fb3 100644
--- a/chrome/browser/tab_group/BUILD.gn
+++ b/chrome/browser/tab_group/BUILD.gn
@@ -52,5 +52,6 @@
     "//third_party/junit",
     "//third_party/mockito:mockito_java",
     "//ui/android:ui_java_test_support",
+    "//url:url_java",
   ]
 }
diff --git a/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java b/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java
index d7f1a262..f902d07 100644
--- a/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java
+++ b/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java
@@ -17,6 +17,7 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tab.TabStateAttributes;
 import org.chromium.chrome.browser.tabmodel.TabList;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelFilter;
@@ -272,8 +273,7 @@
                     removedRootIds.add(tab.getRootId());
                 }
 
-                tab.setRootId(destinationRootId);
-                tab.setTabGroupId(destinationTabGroupId);
+                setBothGroupIds(tab, destinationRootId, destinationTabGroupId);
             }
             resetFilterState();
 
@@ -393,8 +393,7 @@
             }
             boolean isMergingBackward = index < destinationIndexInTabModel;
 
-            tab.setRootId(destinationRootId);
-            tab.setTabGroupId(destinationTabGroupId);
+            setBothGroupIds(tab, destinationRootId, destinationTabGroupId);
             if (index == destinationIndexInTabModel || index + 1 == destinationIndexInTabModel) {
                 // If the tab is not moved TabModelImpl will not invoke
                 // TabModelObserver#didMoveTab() and update events will not be triggered. Call the
@@ -506,6 +505,8 @@
             observer.willMoveTabOutOfGroup(sourceTab, newRootId);
         }
 
+        TabStateAttributes tabStateAttributes = TabStateAttributes.from(sourceTab);
+        tabStateAttributes.beginBatchEdit();
         sourceTab.setTabGroupId(null);
         if (sourceTabIdWasRootId) {
             for (int tabId : sourceTabGroup.getTabIdList()) {
@@ -518,6 +519,7 @@
             resetFilterState();
         }
         sourceTab.setRootId(sourceTab.getId());
+        tabStateAttributes.endBatchEdit();
 
         if (sourceTabIdWasRootId) {
             // Must be done here instead of lower down, as the GTS currently does not listen to
@@ -586,8 +588,7 @@
         // Unconditionally signal removal of the tab from the group it is in.
         mIsUndoing = true;
         boolean groupExistedBeforeMove = mRootIdToGroupMap.get(originalRootId) != null;
-        tab.setRootId(originalRootId);
-        tab.setTabGroupId(originalTabGroupId);
+        setBothGroupIds(tab, originalRootId, originalTabGroupId);
         if (currentIndex == originalIndex) {
             didMoveTab(tab, originalIndex, currentIndex);
         } else {
@@ -1400,4 +1401,12 @@
         }
         return tabGroupId;
     }
+
+    private static void setBothGroupIds(Tab tab, int rootId, Token tabGroupId) {
+        TabStateAttributes tabStateAttributes = TabStateAttributes.from(tab);
+        tabStateAttributes.beginBatchEdit();
+        tab.setRootId(rootId);
+        tab.setTabGroupId(tabGroupId);
+        tabStateAttributes.endBatchEdit();
+    }
 }
diff --git a/chrome/browser/tab_group/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java b/chrome/browser/tab_group/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java
index 2b96b0ab..f64bd8e 100644
--- a/chrome/browser/tab_group/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java
+++ b/chrome/browser/tab_group/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java
@@ -23,8 +23,10 @@
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import static org.chromium.ui.test.util.MockitoHelper.doFunction;
@@ -48,8 +50,10 @@
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.ObserverList;
 import org.chromium.base.Token;
 import org.chromium.base.TokenJni;
+import org.chromium.base.UserDataHost;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Features;
 import org.chromium.base.test.util.Features.DisableFeatures;
@@ -59,11 +63,16 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabCreationState;
 import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tab.TabObserver;
+import org.chromium.chrome.browser.tab.TabStateAttributes;
+import org.chromium.chrome.browser.tab.TabStateAttributes.DirtinessState;
 import org.chromium.chrome.browser.tabmodel.TabList;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.components.tab_groups.TabGroupColorId;
+import org.chromium.ui.test.util.MockitoHelper;
+import org.chromium.url.GURL;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -79,12 +88,12 @@
     ChromeFeatureList.TAB_GROUP_PARITY_ANDROID
 })
 public class TabGroupModelFilterUnitTest {
-    private static final int TAB1_ID = 456;
-    private static final int TAB2_ID = 789;
-    private static final int TAB3_ID = 123;
-    private static final int TAB4_ID = 147;
-    private static final int TAB5_ID = 258;
-    private static final int TAB6_ID = 369;
+    private static final int TAB1_ID = 11;
+    private static final int TAB2_ID = 12;
+    private static final int TAB3_ID = 13;
+    private static final int TAB4_ID = 14;
+    private static final int TAB5_ID = 15;
+    private static final int TAB6_ID = 16;
     private static final int TAB1_ROOT_ID = TAB1_ID;
     private static final int TAB2_ROOT_ID = TAB2_ID;
     private static final int TAB3_ROOT_ID = TAB2_ID;
@@ -110,9 +119,9 @@
     private static final int POSITION5 = 4;
     private static final int POSITION6 = 5;
 
-    private static final int NEW_TAB_ID_0 = 159;
-    private static final int NEW_TAB_ID_1 = 160;
-    private static final int NEW_TAB_ID_2 = 162;
+    private static final int NEW_TAB_ID_0 = 20;
+    private static final int NEW_TAB_ID_1 = 21;
+    private static final int NEW_TAB_ID_2 = 22;
 
     private static final String TAB_GROUP_TITLES_FILE_NAME = "tab_group_titles";
     private static final String TAB_TITLE = "Tab";
@@ -132,6 +141,7 @@
     @Mock SharedPreferences mSharedPreferencesTitle;
     @Mock SharedPreferences mSharedPreferencesColor;
     @Mock SharedPreferences.Editor mEditor;
+    @Mock TabStateAttributes.Observer mAttributesObserver;
 
     @Captor ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
 
@@ -148,13 +158,24 @@
     private InOrder mModelAndObserverInOrder;
 
     private Tab prepareTab(int tabId, int rootId, @Nullable Token tabGroupId, int parentTabId) {
-        Tab tab = mock(Tab.class);
+        Tab tab = mock(Tab.class, "Tab " + tabId);
         doReturn(true).when(tab).isInitialized();
         doReturn(tabId).when(tab).getId();
+
+        ObserverList<TabObserver> tabObserverList = new ObserverList<>();
+        MockitoHelper.doCallback((TabObserver obs) -> tabObserverList.addObserver(obs))
+                .when(tab)
+                .addObserver(any());
+        MockitoHelper.doCallback((TabObserver obs) -> tabObserverList.removeObserver(obs))
+                .when(tab)
+                .removeObserver(any());
         doAnswer(
                         invocation -> {
                             int newRootId = invocation.getArgument(0);
                             when(tab.getRootId()).thenReturn(newRootId);
+                            for (TabObserver observer : tabObserverList) {
+                                observer.onRootIdChanged(tab, newRootId);
+                            }
                             return null;
                         })
                 .when(tab)
@@ -164,12 +185,21 @@
                         invocation -> {
                             Token newTabGroupId = invocation.getArgument(0);
                             when(tab.getTabGroupId()).thenReturn(newTabGroupId);
+                            for (TabObserver observer : tabObserverList) {
+                                observer.onTabGroupIdChanged(tab, newTabGroupId);
+                            }
                             return null;
                         })
                 .when(tab)
                 .setTabGroupId(any());
         tab.setTabGroupId(tabGroupId);
         doReturn(parentTabId).when(tab).getParentId();
+
+        when(tab.getUrl()).thenReturn(GURL.emptyGURL());
+        when(tab.getUserDataHost()).thenReturn(new UserDataHost());
+        TabStateAttributes.createForTab(tab, TabCreationState.LIVE_IN_FOREGROUND);
+        TabStateAttributes.from(tab).addObserver(mAttributesObserver);
+
         return tab;
     }
 
@@ -912,6 +942,8 @@
         assertNull(mTab3.getTabGroupId());
         assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
         assertArrayEquals(mTabs.toArray(), expectedTabModelAfterUngroup.toArray());
+        verify(mAttributesObserver).onTabStateDirtinessChanged(mTab3, DirtinessState.DIRTY);
+        verifyNoMoreInteractions(mAttributesObserver);
     }
 
     @Test
@@ -927,6 +959,9 @@
         assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
         assertNull(mTab2.getTabGroupId());
         assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
+        verify(mAttributesObserver).onTabStateDirtinessChanged(mTab2, DirtinessState.DIRTY);
+        verify(mAttributesObserver).onTabStateDirtinessChanged(mTab3, DirtinessState.DIRTY);
+        verifyNoMoreInteractions(mAttributesObserver);
     }
 
     @Test
@@ -945,6 +980,9 @@
         assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
         assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
         assertThat(mTab4.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
+
+        verify(mAttributesObserver).onTabStateDirtinessChanged(mTab4, DirtinessState.DIRTY);
+        verifyNoMoreInteractions(mAttributesObserver);
     }
 
     @Test
@@ -1134,8 +1172,9 @@
 
         assertThat(mTab5.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
         assertThat(mTab4.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
-        assertThat(mTab1.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
-        assertThat(mTab4.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
+        verify(mAttributesObserver).onTabStateDirtinessChanged(mTab1, DirtinessState.DIRTY);
+        verify(mAttributesObserver).onTabStateDirtinessChanged(mTab4, DirtinessState.DIRTY);
+        verifyNoMoreInteractions(mAttributesObserver);
     }
 
     @Test
@@ -1420,6 +1459,9 @@
         assertThat(mTab4.getRootId(), equalTo(TAB2_ROOT_ID));
         assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(1));
 
+        TabStateAttributes.from(mTab4).clearTabStateDirtiness();
+        reset(mAttributesObserver);
+
         // Undo the grouped action
         mTabGroupModelFilter.undoGroupedTab(mTab4, POSITION4, TAB4_ROOT_ID, TAB4_TAB_GROUP_ID);
 
@@ -1427,6 +1469,8 @@
         assertThat(mTab4.getRootId(), equalTo(TAB4_ROOT_ID));
         assertNull(mTab4.getTabGroupId());
         assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(2));
+        verify(mAttributesObserver).onTabStateDirtinessChanged(mTab4, DirtinessState.DIRTY);
+        verifyNoMoreInteractions(mAttributesObserver);
     }
 
     @Test
diff --git a/chrome/browser/tab_group_sync/BUILD.gn b/chrome/browser/tab_group_sync/BUILD.gn
index 76f9ea0..ca19cf3e 100644
--- a/chrome/browser/tab_group_sync/BUILD.gn
+++ b/chrome/browser/tab_group_sync/BUILD.gn
@@ -39,6 +39,7 @@
       "//third_party/hamcrest:hamcrest_core_java",
       "//third_party/hamcrest:hamcrest_library_java",
       "//third_party/junit:junit",
+      "//url:url_java",
     ]
   }
 
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncServiceFactoryTest.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncServiceFactoryTest.java
index 17623304..096525ed 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncServiceFactoryTest.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncServiceFactoryTest.java
@@ -6,6 +6,7 @@
 
 import static org.chromium.base.test.util.Batch.PER_CLASS;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Assert;
@@ -23,7 +24,9 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.profiles.ProfileManager;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.components.tab_group_sync.SavedTabGroup;
 import org.chromium.components.tab_group_sync.TabGroupSyncService;
+import org.chromium.url.GURL;
 
 import java.util.concurrent.TimeoutException;
 
@@ -48,7 +51,47 @@
                     public void removeObserver(Observer observer) {}
 
                     @Override
+                    public void createGroup(int groupId) {}
+
+                    @Override
                     public void removeGroup(int groupId) {}
+
+                    @Override
+                    public void updateVisualData(
+                            int tabGroupId, @NonNull String title, int color) {}
+
+                    @Override
+                    public void addTab(
+                            int tabGroupId, int tabId, String title, GURL url, int position) {}
+
+                    @Override
+                    public void updateTab(
+                            int tabGroupId, int tabId, String title, GURL url, int position) {}
+
+                    @Override
+                    public void removeTab(int tabGroupId, int tabId) {}
+
+                    @Override
+                    public String[] getAllGroupIds() {
+                        return new String[0];
+                    }
+
+                    @Override
+                    public SavedTabGroup getGroup(String syncGroupId) {
+                        return null;
+                    }
+
+                    @Override
+                    public SavedTabGroup getGroup(int localGroupId) {
+                        return null;
+                    }
+
+                    @Override
+                    public void updateLocalTabGroupId(String syncId, int localId) {}
+
+                    @Override
+                    public void updateLocalTabId(
+                            int localGroupId, String syncTabId, int localTabId) {}
                 };
 
         TabGroupSyncServiceFactory.setForTesting(testService);
diff --git a/chrome/browser/tracing/background_tracing_field_trial_unittest.cc b/chrome/browser/tracing/background_tracing_field_trial_unittest.cc
index 0d5ef4ef..a0a1cbe5 100644
--- a/chrome/browser/tracing/background_tracing_field_trial_unittest.cc
+++ b/chrome/browser/tracing/background_tracing_field_trial_unittest.cc
@@ -29,12 +29,16 @@
 class BackgroundTracingTest : public testing::Test {
  public:
   BackgroundTracingTest() {
+    testing_profile_manager_ = std::make_unique<TestingProfileManager>(
+        TestingBrowserProcess::GetGlobal());
+
     background_tracing_manager_ =
         content::BackgroundTracingManager::CreateInstance();
   }
 
+  void SetUp() override { ASSERT_TRUE(testing_profile_manager_->SetUp()); }
+
   void TearDown() override {
-    tracing::BackgroundTracingStateManager::GetInstance().ResetForTesting();
     content::BackgroundTracingManager::GetInstance().AbortScenarioForTesting();
   }
 
@@ -103,10 +107,6 @@
                                   {{"config", kValidJsonTracingConfig}});
   base::FieldTrialList::CreateFieldTrial(kTrialName, kExperimentName);
 
-  testing_profile_manager_ = std::make_unique<TestingProfileManager>(
-      TestingBrowserProcess::GetGlobal());
-  ASSERT_TRUE(testing_profile_manager_->SetUp());
-
   ASSERT_EQ(tracing::GetBackgroundTracingSetupMode(),
             BackgroundTracingSetupMode::kFromFieldTrial);
   EXPECT_FALSE(tracing::MaybeSetupSystemTracingFromFieldTrial());
@@ -123,10 +123,6 @@
   scoped_list.InitAndEnableFeatureWithParameters(tracing::kFieldTracing,
                                                  {{"config", encoded_config}});
 
-  testing_profile_manager_ = std::make_unique<TestingProfileManager>(
-      TestingBrowserProcess::GetGlobal());
-  ASSERT_TRUE(testing_profile_manager_->SetUp());
-
   ASSERT_EQ(tracing::GetBackgroundTracingSetupMode(),
             BackgroundTracingSetupMode::kFromFieldTrial);
   EXPECT_FALSE(tracing::MaybeSetupSystemTracingFromFieldTrial());
@@ -134,10 +130,6 @@
 }
 
 TEST_F(BackgroundTracingTest, SetupBackgroundTracingFromProtoConfigFile) {
-  testing_profile_manager_ = std::make_unique<TestingProfileManager>(
-      TestingBrowserProcess::GetGlobal());
-  ASSERT_TRUE(testing_profile_manager_->SetUp());
-
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
   base::FilePath file_path = temp_dir.GetPath().AppendASCII("config.pb");
@@ -159,10 +151,6 @@
 }
 
 TEST_F(BackgroundTracingTest, SetupBackgroundTracingFromJsonConfigFile) {
-  testing_profile_manager_ = std::make_unique<TestingProfileManager>(
-      TestingBrowserProcess::GetGlobal());
-  ASSERT_TRUE(testing_profile_manager_->SetUp());
-
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
   base::FilePath file_path = temp_dir.GetPath().AppendASCII("config.json");
@@ -190,10 +178,6 @@
                                   {{"config", kValidJsonTracingConfig}});
   base::FieldTrialList::CreateFieldTrial(kTrialName, kExperimentName);
 
-  testing_profile_manager_ = std::make_unique<TestingProfileManager>(
-      TestingBrowserProcess::GetGlobal());
-  ASSERT_TRUE(testing_profile_manager_->SetUp());
-
   EXPECT_FALSE(
       content::BackgroundTracingManager::GetInstance().HasActiveScenario());
 
diff --git a/chrome/browser/tracing/chrome_tracing_delegate.cc b/chrome/browser/tracing/chrome_tracing_delegate.cc
index cec9ba0..6750e2d 100644
--- a/chrome/browser/tracing/chrome_tracing_delegate.cc
+++ b/chrome/browser/tracing/chrome_tracing_delegate.cc
@@ -104,7 +104,9 @@
 
 }  // namespace
 
-ChromeTracingDelegate::ChromeTracingDelegate() {
+ChromeTracingDelegate::ChromeTracingDelegate()
+    : state_manager_(tracing::BackgroundTracingStateManager::CreateInstance(
+          g_browser_process->local_state())) {
   // Ensure that this code is called on the UI thread, except for
   // tests where a UI thread might not have been initialized at this point.
   DCHECK(
@@ -182,19 +184,8 @@
 
 bool ChromeTracingDelegate::OnBackgroundTracingActive(
     bool requires_anonymized_data) {
-  // We call Initialize() only when a tracing scenario tries to start, and
-  // unless this happens we never save state. In particular, if the background
-  // tracing experiment is disabled, Initialize() will never be called, and we
-  // will thus not save state. This means that when we save the background
-  // tracing session state for one session, and then later read the state in a
-  // future session, there might have been sessions between these two where
-  // tracing was disabled. Therefore, when IsActionAllowed records
-  // TracingFinalizationDisallowedReason::kLastTracingSessionDidNotEnd, it
-  // might not be the directly preceding session, but instead it is the
-  // previous session where tracing was enabled.
   BackgroundTracingStateManager& state =
       BackgroundTracingStateManager::GetInstance();
-  state.Initialize(g_browser_process->local_state());
 
   if (!IsActionAllowed(BackgroundScenarioAction::kStartTracing,
                        requires_anonymized_data)) {
diff --git a/chrome/browser/tracing/chrome_tracing_delegate.h b/chrome/browser/tracing/chrome_tracing_delegate.h
index 4ee7e83..ff5fe5d 100644
--- a/chrome/browser/tracing/chrome_tracing_delegate.h
+++ b/chrome/browser/tracing/chrome_tracing_delegate.h
@@ -19,6 +19,10 @@
 #include "chrome/browser/ui/browser_list_observer.h"
 #endif
 
+namespace tracing {
+class BackgroundTracingStateManager;
+}
+
 class ChromeTracingDelegate : public content::TracingDelegate,
 #if BUILDFLAG(IS_ANDROID)
                               public TabModelListObserver
@@ -27,6 +31,10 @@
 #endif
 {
  public:
+  // Whether system-wide performance trace collection using the external system
+  // tracing service is enabled.
+  static bool IsSystemWideTracingEnabled();
+
   ChromeTracingDelegate();
   ~ChromeTracingDelegate() override;
 
@@ -44,7 +52,6 @@
   bool OnBackgroundTracingIdle(bool requires_anonymized_data) override;
 
   bool ShouldSaveUnuploadedTrace() const override;
-  bool IsSystemWideTracingEnabled() override;
 
  private:
   FRIEND_TEST_ALL_PREFIXES(ChromeTracingDelegateBrowserTest,
@@ -85,6 +92,8 @@
                        bool requires_anonymized_data) const;
 
   bool incognito_launched_ = false;
+
+  std::unique_ptr<tracing::BackgroundTracingStateManager> state_manager_;
 };
 
 #endif  // CHROME_BROWSER_TRACING_CHROME_TRACING_DELEGATE_H_
diff --git a/chrome/browser/tracing/chrome_tracing_delegate_browsertest.cc b/chrome/browser/tracing/chrome_tracing_delegate_browsertest.cc
index dbc9447..06cb1a49 100644
--- a/chrome/browser/tracing/chrome_tracing_delegate_browsertest.cc
+++ b/chrome/browser/tracing/chrome_tracing_delegate_browsertest.cc
@@ -85,15 +85,9 @@
     PrefService* local_state = g_browser_process->local_state();
     DCHECK(local_state);
     local_state->SetBoolean(metrics::prefs::kMetricsReportingEnabled, true);
-    tracing::BackgroundTracingStateManager::GetInstance()
-        .SetPrefServiceForTesting(local_state);
     content::TracingController::GetInstance();  // Create tracing agents.
   }
 
-  void TearDownOnMainThread() override {
-    tracing::BackgroundTracingStateManager::GetInstance().ResetForTesting();
-  }
-
   bool StartPreemptiveScenario(
       content::BackgroundTracingManager::DataFiltering data_filtering,
       base::StringPiece scenario_name = "TestScenario",
@@ -155,13 +149,18 @@
   return json;
 }
 
+void SetSessionState(base::Value::Dict dict) {
+  PrefService* local_state = g_browser_process->local_state();
+  local_state->Set(tracing::kBackgroundTracingSessionState,
+                   base::Value(std::move(dict)));
+}
+
 IN_PROC_BROWSER_TEST_F(ChromeTracingDelegateBrowserTest,
                        BackgroundTracingUnexpectedSessionEnd) {
-  std::string state = GetSessionStateJson();
-  EXPECT_EQ(state, "{}");
-
-  tracing::BackgroundTracingStateManager::GetInstance().SaveState(
-      tracing::BackgroundTracingState::STARTED);
+  base::Value::Dict dict;
+  dict.Set("state", static_cast<int>(tracing::BackgroundTracingState::STARTED));
+  SetSessionState(std::move(dict));
+  tracing::BackgroundTracingStateManager::GetInstance().ResetForTesting();
 
   EXPECT_FALSE(StartPreemptiveScenario(
       content::BackgroundTracingManager::NO_DATA_FILTERING));
@@ -169,11 +168,11 @@
 
 IN_PROC_BROWSER_TEST_F(ChromeTracingDelegateBrowserTest,
                        BackgroundTracingSessionRanLong) {
-  std::string state = GetSessionStateJson();
-  EXPECT_EQ(state, "{}");
-
-  tracing::BackgroundTracingStateManager::GetInstance().SaveState(
-      tracing::BackgroundTracingState::RAN_30_SECONDS);
+  base::Value::Dict dict;
+  dict.Set("state",
+           static_cast<int>(tracing::BackgroundTracingState::RAN_30_SECONDS));
+  SetSessionState(std::move(dict));
+  tracing::BackgroundTracingStateManager::GetInstance().ResetForTesting();
 
   EXPECT_TRUE(StartPreemptiveScenario(
       content::BackgroundTracingManager::NO_DATA_FILTERING));
@@ -181,31 +180,16 @@
 
 IN_PROC_BROWSER_TEST_F(ChromeTracingDelegateBrowserTest,
                        BackgroundTracingFinalizationStarted) {
-  std::string state = GetSessionStateJson();
-  EXPECT_EQ(state, "{}");
-
-  tracing::BackgroundTracingStateManager::GetInstance().SaveState(
-      tracing::BackgroundTracingState::FINALIZATION_STARTED);
+  base::Value::Dict dict;
+  dict.Set("state", static_cast<int>(
+                        tracing::BackgroundTracingState::FINALIZATION_STARTED));
+  SetSessionState(std::move(dict));
+  tracing::BackgroundTracingStateManager::GetInstance().ResetForTesting();
 
   EXPECT_TRUE(StartPreemptiveScenario(
       content::BackgroundTracingManager::NO_DATA_FILTERING));
 }
 
-IN_PROC_BROWSER_TEST_F(ChromeTracingDelegateBrowserTest,
-                       BackgroundTracingFinalizationBefore30Seconds) {
-  std::string state = GetSessionStateJson();
-  EXPECT_EQ(state, "{}");
-
-  tracing::BackgroundTracingStateManager::GetInstance().SaveState(
-      tracing::BackgroundTracingState::FINALIZATION_STARTED);
-
-  // State does not update from finalization started to ran 30 seconds.
-  tracing::BackgroundTracingStateManager::GetInstance().SaveState(
-      tracing::BackgroundTracingState::RAN_30_SECONDS);
-  state = GetSessionStateJson();
-  EXPECT_EQ(state, R"({"state":2})");
-}
-
 // If we need a PII-stripped trace, any existing OTR session should block the
 // trace.
 IN_PROC_BROWSER_TEST_F(ChromeTracingDelegateBrowserTest,
@@ -285,7 +269,7 @@
   EXPECT_TRUE(
       content::BackgroundTracingManager::GetInstance().HasActiveScenario());
   // State 1 = STARTED.
-  EXPECT_EQ(GetSessionStateJson(), R"({"state":1})");
+  EXPECT_EQ(GetSessionStateJson(), R"({"privacy_filter":true,"state":1})");
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeTracingDelegateBrowserTestOnStartup,
@@ -295,7 +279,7 @@
   EXPECT_FALSE(
       content::BackgroundTracingManager::GetInstance().HasActiveScenario());
   // State 0 = NOT_ACTIVATED, current session is inactive.
-  EXPECT_EQ(GetSessionStateJson(), R"({"state":0})");
+  EXPECT_EQ(GetSessionStateJson(), R"({"privacy_filter":true,"state":0})");
 }
 
 class ChromeTracingDelegateBrowserTestFromCommandLine
@@ -349,7 +333,7 @@
   EXPECT_TRUE(
       content::BackgroundTracingManager::GetInstance().HasActiveScenario());
   // State 1 = STARTED.
-  EXPECT_EQ(GetSessionStateJson(), R"({"state":1})");
+  EXPECT_EQ(GetSessionStateJson(), R"({"privacy_filter":true,"state":1})");
 
   // The scenario should also be "uploaded" (actually written to the output
   // file).
@@ -361,7 +345,7 @@
                        PRE_IgnoreThrottle) {
   EXPECT_TRUE(
       content::BackgroundTracingManager::GetInstance().HasActiveScenario());
-  EXPECT_EQ(GetSessionStateJson(), R"({"state":1})");
+  EXPECT_EQ(GetSessionStateJson(), R"({"privacy_filter":true,"state":1})");
 
   // This updates the upload time for the test scenario to the current time,
   // even though the output is actually written to a file.
@@ -369,7 +353,7 @@
   EXPECT_TRUE(OutputPathExists());
 
   std::string state = GetSessionStateJson();
-  EXPECT_TRUE(base::MatchPattern(state, R"({"state":3})"))
+  EXPECT_TRUE(base::MatchPattern(state, R"({"privacy_filter":true,"state":3})"))
       << "Actual: " << state;
 }
 
@@ -383,7 +367,7 @@
       content::BackgroundTracingManager::GetInstance().HasActiveScenario());
   // State 1 = STARTED.
   std::string state = GetSessionStateJson();
-  EXPECT_TRUE(base::MatchPattern(state, R"({"state":1})"))
+  EXPECT_TRUE(base::MatchPattern(state, R"({"privacy_filter":true,"state":1})"))
       << "Actual: " << state;
 
   // The scenario should also be "uploaded" (actually written to the output
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index b3ec5cd6..0ad697d9 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -5265,6 +5265,8 @@
       "views/controls/rich_controls_container_view.h",
       "views/controls/rich_hover_button.cc",
       "views/controls/rich_hover_button.h",
+      "views/controls/site_icon_text_and_origin_view.cc",
+      "views/controls/site_icon_text_and_origin_view.h",
       "views/controls/subpage_view.cc",
       "views/controls/subpage_view.h",
       "views/desktop_capture/desktop_media_list_controller.cc",
@@ -6484,6 +6486,8 @@
         "views/permissions/permission_prompt_notifications_mac.h",
         "views/policy/enterprise_startup_dialog_mac_util.h",
         "views/policy/enterprise_startup_dialog_mac_util.mm",
+        "views/webauthn/authenticator_touch_id_view.cc",
+        "views/webauthn/authenticator_touch_id_view.h",
         "views/webauthn/mac_authentication_view.h",
         "views/webauthn/mac_authentication_view.mm",
       ]
diff --git a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/components/VirtualView.java b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/components/VirtualView.java
index 6adc8d0..841ba203 100644
--- a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/components/VirtualView.java
+++ b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/components/VirtualView.java
@@ -28,6 +28,20 @@
     boolean checkClickedOrHovered(float x, float y);
 
     /**
+     * @return Whether there is a click action associated with this virtual view.
+     */
+    default boolean hasClickAction() {
+        return true;
+    }
+
+    /**
+     * @return Whether there is a long click action associated with this virtual view.
+     */
+    default boolean hasLongClickAction() {
+        return true;
+    }
+
+    /**
      * Notifies the view to handle the click action.
      *
      * @param time The time of the click action.
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
index a48477b..3a6f392 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
@@ -54,7 +54,6 @@
     private Runnable mKeyboardResizeModeTask = NO_OP_RUNNABLE;
     private Runnable mKeyboardHideTask = NO_OP_RUNNABLE;
     private Callback<Boolean> mFocusChangeCallback;
-    private boolean mShouldShowModernizeVisualUpdate;
 
     /**
      * Constructs a coordinator for the given UrlBar view.
@@ -220,11 +219,9 @@
     // KeyboardVisibilityDelegate.KeyboardVisibilityListener implementation.
     @Override
     public void keyboardVisibilityChanged(boolean isKeyboardShowing) {
-        if (mShouldShowModernizeVisualUpdate) {
-            // The cursor visibility should follow soft keyboard visibility and should be hidden
-            // when keyboard is dismissed for any reason (including scroll).
-            mUrlBar.setCursorVisible(isKeyboardShowing);
-        }
+        // The cursor visibility should follow soft keyboard visibility and should be hidden
+        // when keyboard is dismissed for any reason (including scroll).
+        mUrlBar.setCursorVisible(isKeyboardShowing);
     }
 
     /* package */ boolean hasFocus() {
@@ -309,8 +306,6 @@
     /** Signals that's it safe to call code that requires native to be loaded. */
     public void onFinishNativeInitialization() {
         mUrlBar.onFinishNativeInitialization();
-        mShouldShowModernizeVisualUpdate =
-                OmniboxFeatures.shouldShowModernizeVisualUpdate(mUrlBar.getContext());
     }
 
     /**
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
index 544255c..b23fd07 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
@@ -62,8 +62,8 @@
          *     always comes in last, even if the query is canceled.
          */
         void onSuggestionsReceived(
-                AutocompleteResult autocompleteResult,
-                String inlineAutocompleteText,
+                @NonNull AutocompleteResult autocompleteResult,
+                @NonNull String inlineAutocompleteText,
                 boolean isFinal);
     }
 
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
index 7679bc05..652810b 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
@@ -374,14 +374,6 @@
     }
 
     /**
-     * Show cached zero suggest results. Enables Autocomplete subsystem to offer most recently
-     * presented suggestions in the event where Native counterpart is not yet initialized.
-     */
-    public void startCachedZeroSuggest() {
-        mMediator.startCachedZeroSuggest();
-    }
-
-    /**
      * Handle the key events associated with the suggestion list.
      *
      * @param keyCode The keycode representing what key was interacted with.
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index 5469feb..b9355730 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -111,9 +111,9 @@
     private @Nullable TemplateUrlService mTemplateUrlService;
 
     private boolean mNativeInitialized;
+    private boolean mIsInZeroPrefixContext;
     private AutocompleteController mAutocomplete;
     private long mUrlFocusTime;
-    private boolean mShouldCacheSuggestions;
     private boolean mClearFocusAfterNavigation;
     private boolean mClearFocusAfterNavigationAsynchronously;
     // When set, indicates an active omnibox session.
@@ -320,7 +320,27 @@
      * <p>Note: the only supported page context right now is the ANDROID_SEARCH_WIDGET.
      */
     void startCachedZeroSuggest() {
-        if (mNativeInitialized) return;
+        maybeServeCachedResult();
+        postAutocompleteRequest(this::startZeroSuggest, SCHEDULE_FOR_IMMEDIATE_EXECUTION);
+    }
+
+    private void maybeCacheResult(@NonNull AutocompleteResult result) {
+        if (mIsInZeroPrefixContext
+                && !result.isFromCachedResult()
+                && mDataProvider.getPageClassification(false, false)
+                        == PageClassification.ANDROID_SEARCH_WIDGET_VALUE) {
+            CachedZeroSuggestionsManager.saveToCache(result);
+        }
+    }
+
+    private void maybeServeCachedResult() {
+        int pageClass = mDataProvider.getPageClassification(false, false);
+        if (mNativeInitialized
+                || !mIsInZeroPrefixContext
+                || (pageClass != PageClassification.ANDROID_SEARCH_WIDGET_VALUE
+                        && pageClass != PageClassification.ANDROID_SHORTCUTS_WIDGET_VALUE)) {
+            return;
+        }
         onSuggestionsReceived(CachedZeroSuggestionsManager.readFromCache(), "", true);
     }
 
@@ -377,20 +397,14 @@
 
             // Ask directly for zero-suggestions related to current input, unless the user is
             // currently visiting SearchActivity and the input is populated from the launch intent.
-            // For SearchActivity, in most cases the input will be empty, triggering the same
-            // response (starting zero suggestions), but if the Activity was launched with a QUERY,
+            // In all contexts, the input will most likely be empty, triggering the same response
+            // (starting zero suggestions), but if the SearchActivity was launched with a QUERY,
             // then the query might point to a different URL than the reported Page, and the
             // suggestion would take the user to the DSE home page.
             // This is tracked by MobileStartup.LaunchCause / EXTERNAL_SEARCH_ACTION_INTENT
             // metric.
-            if (mDataProvider.getPageClassification(
-                            /* isFocusedFromFakebox= */ false, /* isPrefetch= */ false)
-                    != PageClassification.ANDROID_SEARCH_WIDGET_VALUE) {
-                postAutocompleteRequest(this::startZeroSuggest, SCHEDULE_FOR_IMMEDIATE_EXECUTION);
-            } else {
-                String text = mUrlBarEditingTextProvider.getTextWithoutAutocomplete();
-                onTextChanged(text);
-            }
+            String text = mUrlBarEditingTextProvider.getTextWithoutAutocomplete();
+            onTextChanged(text);
         } else {
             stopMeasuringSuggestionRequestToUiModelTime();
             cancelAutocompleteRequests();
@@ -770,9 +784,11 @@
         }
 
         stopAutocomplete(false);
-        if (TextUtils.isEmpty(textWithoutAutocomplete)) {
+        mIsInZeroPrefixContext = TextUtils.isEmpty(textWithoutAutocomplete);
+
+        if (mIsInZeroPrefixContext) {
             hideSuggestions();
-            postAutocompleteRequest(this::startZeroSuggest, SCHEDULE_FOR_IMMEDIATE_EXECUTION);
+            startCachedZeroSuggest();
         } else {
             // There may be no tabs when searching form omnibox in overview mode. In that case,
             // LocationBarDataProvider.getCurrentUrl() returns NTP url.
@@ -807,10 +823,10 @@
 
     @Override
     public void onSuggestionsReceived(
-            AutocompleteResult autocompleteResult, String inlineAutocompleteText, boolean isFinal) {
-        if (mShouldCacheSuggestions) {
-            CachedZeroSuggestionsManager.saveToCache(autocompleteResult);
-        }
+            @NonNull AutocompleteResult autocompleteResult,
+            @NonNull String inlineAutocompleteText,
+            boolean isFinal) {
+        maybeCacheResult(autocompleteResult);
 
         final List<AutocompleteMatch> newSuggestions = autocompleteResult.getSuggestionsList();
         String userText = mUrlBarEditingTextProvider.getTextWithoutAutocomplete();
@@ -1032,8 +1048,6 @@
             int pageClassification =
                     mDataProvider.getPageClassification(
                             mDelegate.didFocusUrlFromFakebox(), /* isPrefetch= */ false);
-            mShouldCacheSuggestions =
-                    pageClassification == PageClassification.ANDROID_SEARCH_WIDGET_VALUE;
             mAutocomplete.startZeroSuggest(
                     mUrlBarEditingTextProvider.getTextWithAutocomplete(),
                     mDataProvider.getCurrentGurl(),
@@ -1230,7 +1244,6 @@
 
     /** Cancel any pending autocomplete actions. */
     private void cancelAutocompleteRequests() {
-        mShouldCacheSuggestions = false;
         stopMeasuringSuggestionRequestToUiModelTime();
         if (mCurrentAutocompleteRequest != null) {
             mHandler.removeCallbacks(mCurrentAutocompleteRequest);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
index e809ca1..304fa45 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
@@ -11,11 +11,13 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -92,12 +94,14 @@
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /** Tests for {@link AutocompleteMediator}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(
         manifest = Config.NONE,
         shadows = {
+            AutocompleteMediatorUnitTest.ShadowCachedSuggestionsManager.class,
             AutocompleteMediatorUnitTest.ShadowTemplateUrlServiceFactory.class,
             ShadowLooper.class
         })
@@ -108,10 +112,12 @@
 public class AutocompleteMediatorUnitTest {
     private static final int SUGGESTION_MIN_HEIGHT = 20;
     private static final int HEADER_MIN_HEIGHT = 15;
+    private static final GURL PAGE_URL = new GURL("https://www.site.com/page.html");
+    private static final String PAGE_TITLE = "Page Title";
 
     public @Rule TestRule mProcessor = new Features.JUnitProcessor();
     public @Rule JniMocker mJniMocker = new JniMocker();
-    public @Rule MockitoRule mockitoRule = MockitoJUnit.rule();
+    public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     private @Mock AutocompleteDelegate mAutocompleteDelegate;
     private @Mock UrlBarEditingTextStateProvider mTextStateProvider;
@@ -155,6 +161,32 @@
         }
     }
 
+    // Interface abstracting calls to CachedZeroSuggestionsManager, making interactions with that
+    // more idiomatic.
+    interface CachedZeroSuggestionsManagerCalls {
+        void saveToCache(AutocompleteResult r);
+
+        AutocompleteResult readFromCache();
+    }
+
+    // CachedZeroSuggestionsManager shadow that helps us intercept interactions with manager's
+    // static methods.
+    @Implements(CachedZeroSuggestionsManager.class)
+    public static class ShadowCachedSuggestionsManager {
+        public static CachedZeroSuggestionsManagerCalls mock =
+                mock(CachedZeroSuggestionsManagerCalls.class);
+
+        @Implementation
+        public static void saveToCache(AutocompleteResult r) {
+            mock.saveToCache(r);
+        }
+
+        @Implementation
+        public static AutocompleteResult readFromCache() {
+            return mock.readFromCache();
+        }
+    }
+
     @Before
     public void setUp() {
         mJniMocker.mock(LargeIconBridgeJni.TEST_HOOKS, mLargeIconBridgeJniMock);
@@ -209,7 +241,7 @@
         doReturn(OmniboxSuggestionUiType.HEADER).when(mMockHeaderProcessor).getViewTypeId();
 
         mSuggestionsList = buildSampleSuggestionsList(10, "Suggestion");
-        mAutocompleteResult = AutocompleteResult.fromCache(mSuggestionsList, null);
+        mAutocompleteResult = spy(AutocompleteResult.fromCache(mSuggestionsList, null));
         doReturn(true).when(mAutocompleteDelegate).isKeyboardActive();
         setUpLocationBarDataProvider(
                 JUnitTestGURLs.NTP_URL, "New Tab Page", PageClassification.NTP_VALUE);
@@ -1329,4 +1361,108 @@
         Assert.assertEquals(mMediator.getEditSessionStateForTest(), EditSessionState.INACTIVE);
         verify(mAutocompleteDelegate, times(1)).clearOmniboxFocus();
     }
+
+    @Test
+    public void onTextChanged_cachedZpsEligibleOnSelectPageClasses() {
+        Set<Integer> eligibleClasses =
+                Set.of(
+                        PageClassification.ANDROID_SEARCH_WIDGET_VALUE,
+                        PageClassification.ANDROID_SHORTCUTS_WIDGET_VALUE);
+
+        doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
+
+        for (var pageClass : PageClassification.values()) {
+            setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
+
+            mMediator.onTextChanged("");
+
+            // Should only be invoked if page class is eligible.
+            int numTimesInvoked = eligibleClasses.contains(pageClass.getNumber()) ? 1 : 0;
+            verify(ShadowCachedSuggestionsManager.mock, times(numTimesInvoked)).readFromCache();
+            verify(ShadowCachedSuggestionsManager.mock, never()).saveToCache(any());
+
+            clearInvocations(ShadowCachedSuggestionsManager.mock);
+        }
+    }
+
+    @Test
+    public void onTextChanged_cachedZpsNotInvokedInTypedContext() {
+        doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
+
+        for (var pageClass : PageClassification.values()) {
+            setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
+
+            mMediator.onTextChanged("text");
+
+            // Should only be invoked if page class is eligible.
+            verify(ShadowCachedSuggestionsManager.mock, never()).readFromCache();
+            verify(ShadowCachedSuggestionsManager.mock, never()).saveToCache(any());
+
+            clearInvocations(ShadowCachedSuggestionsManager.mock);
+        }
+    }
+
+    @Test
+    public void onTextChanged_cachedZpsNotInvokedWithNativeReady() {
+        doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
+        mMediator.onNativeInitialized();
+
+        for (var pageClass : PageClassification.values()) {
+            setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
+
+            mMediator.onTextChanged("");
+
+            // Should only be invoked if page class is eligible.
+            verify(ShadowCachedSuggestionsManager.mock, never()).readFromCache();
+            verify(ShadowCachedSuggestionsManager.mock, never()).saveToCache(any());
+
+            clearInvocations(ShadowCachedSuggestionsManager.mock);
+        }
+    }
+
+    @Test
+    public void onTextChanged_cacheZpsFromEligiblePageClasses() {
+        Set<Integer> eligibleClasses = Set.of(PageClassification.ANDROID_SEARCH_WIDGET_VALUE);
+
+        doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
+        doReturn(false).when(mAutocompleteResult).isFromCachedResult();
+
+        for (var pageClass : PageClassification.values()) {
+            setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
+
+            mMediator.onTextChanged("");
+
+            // Should only be invoked if page class is eligible.
+            int numTimesInvoked = eligibleClasses.contains(pageClass.getNumber()) ? 1 : 0;
+            verify(ShadowCachedSuggestionsManager.mock, times(numTimesInvoked)).saveToCache(any());
+
+            clearInvocations(ShadowCachedSuggestionsManager.mock);
+        }
+    }
+
+    @Test
+    public void onTextChanged_dontCacheTypedSuggestions() {
+        doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
+        doReturn(false).when(mAutocompleteResult).isFromCachedResult();
+
+        for (var pageClass : PageClassification.values()) {
+            setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
+            mMediator.onTextChanged("x");
+            verify(ShadowCachedSuggestionsManager.mock, never()).saveToCache(any());
+            clearInvocations(ShadowCachedSuggestionsManager.mock);
+        }
+    }
+
+    @Test
+    public void onTextChanged_dontCacheCachedSuggestions() {
+        doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
+        doReturn(true).when(mAutocompleteResult).isFromCachedResult();
+
+        for (var pageClass : PageClassification.values()) {
+            setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
+            mMediator.onTextChanged("");
+            verify(ShadowCachedSuggestionsManager.mock, never()).saveToCache(any());
+            clearInvocations(ShadowCachedSuggestionsManager.mock);
+        }
+    }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/CachedZeroSuggestionsManager.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/CachedZeroSuggestionsManager.java
index 6eba256..66c9ac5 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/CachedZeroSuggestionsManager.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/CachedZeroSuggestionsManager.java
@@ -46,7 +46,7 @@
 /** CachedZeroSuggestionsManager manages caching and restoring zero suggestions. */
 public class CachedZeroSuggestionsManager {
     /** Save the content of the CachedZeroSuggestionsManager to SharedPreferences cache. */
-    public static void saveToCache(AutocompleteResult resultToCache) {
+    public static void saveToCache(@NonNull AutocompleteResult resultToCache) {
         final SharedPreferencesManager manager = ChromeSharedPreferences.getInstance();
         cacheSuggestionList(manager, resultToCache.getSuggestionsList());
         cacheGroupsDetails(manager, resultToCache.getGroupsInfo());
@@ -57,7 +57,7 @@
      *
      * @return AutocompleteResult populated with the content of the SharedPreferences cache.
      */
-    static AutocompleteResult readFromCache() {
+    static @NonNull AutocompleteResult readFromCache() {
         final SharedPreferencesManager manager = ChromeSharedPreferences.getInstance();
         List<AutocompleteMatch> suggestions =
                 CachedZeroSuggestionsManager.readCachedSuggestionList(manager);
diff --git a/chrome/browser/ui/android/theme/java/res/values/colors.xml b/chrome/browser/ui/android/theme/java/res/values/colors.xml
index 52c8861..b627635 100644
--- a/chrome/browser/ui/android/theme/java/res/values/colors.xml
+++ b/chrome/browser/ui/android/theme/java/res/values/colors.xml
@@ -9,6 +9,6 @@
     <!-- Toolbar colors -->
     <color name="toolbar_text_box_background_incognito">@color/baseline_neutral_0</color>
     <color name="toolbar_hairline_overlay">@color/baseline_neutral_0_alpha_10</color>
-    <color name="toolbar_icon_unfocused_activity_incognito_color">@color/baseline_neutral_90_alpha_55</color>
+    <color name="toolbar_icon_unfocused_activity_incognito_color">@color/baseline_neutral_90_alpha_38</color>
 
 </resources>
diff --git a/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/ThemeUtils.java b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/ThemeUtils.java
index 7eac01a..df82353d 100644
--- a/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/ThemeUtils.java
+++ b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/ThemeUtils.java
@@ -178,7 +178,6 @@
      */
     public static @ColorRes int getThemedToolbarIconTintResForActivityState(
             @BrandedColorScheme int brandedColorScheme, boolean isActivityFocused) {
-        // TODO(crbug.com/328054353): Update unfocused activity tint once finalized.
         @ColorRes
         int colorId =
                 isActivityFocused
diff --git a/chrome/browser/ui/ash/calendar/calendar_keyed_service_unittest.cc b/chrome/browser/ui/ash/calendar/calendar_keyed_service_unittest.cc
index d5d5cd5..92f58bf 100644
--- a/chrome/browser/ui/ash/calendar/calendar_keyed_service_unittest.cc
+++ b/chrome/browser/ui/ash/calendar/calendar_keyed_service_unittest.cc
@@ -18,6 +18,7 @@
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/account_id/account_id.h"
 #include "components/sync_preferences/pref_service_syncable.h"
+#include "google_apis/calendar/calendar_api_requests.h"
 #include "google_apis/common/api_error_codes.h"
 #include "google_apis/common/dummy_auth_service.h"
 #include "google_apis/common/test_util.h"
@@ -304,14 +305,24 @@
   }
 
   EXPECT_EQ(google_apis::HTTP_SUCCESS, error);
-  const google_apis::calendar::CalendarEvent& event = *events->items()[2];
+  const google_apis::calendar::CalendarEvent& event = *events->items()[1];
   // Verify that a returned event matches one at the same position on the mock
   // group calendar events list.
-  EXPECT_EQ(event.summary(), "Popcorn Pop-Up");
-  EXPECT_EQ(event.id(), "kff9ghr5gt8fhhechomqnld9et");
+  EXPECT_EQ(event.summary(), "Strawberry Refreshers @ 4th Floor Cafe");
+  EXPECT_EQ(event.id(), "z27t6aqhloxm19wpwcrk7gyycy");
+  // Verify that the event color ID has not been injected with the value passed
+  // in for calendar_color_id because the field was already populated.
+  EXPECT_EQ(event.color_id(), "7");
+
+  const google_apis::calendar::CalendarEvent& event2 = *events->items()[2];
+  // Verify that a returned event matches one at the same position on the mock
+  // group calendar events list.
+  EXPECT_EQ(event2.summary(), "Popcorn Pop-Up");
+  EXPECT_EQ(event2.id(), "kff9ghr5gt8fhhechomqnld9et");
   // Verify that the event color ID is now equal to the value passed into
-  // calendar_color_id.
-  EXPECT_EQ(event.color_id(), kTestGroupCalendarColorId);
+  // calendar_color_id (prepended by a marker).
+  EXPECT_EQ(event2.color_id(), google_apis::calendar::kInjectedColorIdPrefix +
+                                   kTestGroupCalendarColorId);
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_promise_app_shelf_context_menu_browsertest.cc b/chrome/browser/ui/ash/shelf/app_service/app_service_promise_app_shelf_context_menu_browsertest.cc
index 6d8d868f..84b4b20 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_promise_app_shelf_context_menu_browsertest.cc
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_promise_app_shelf_context_menu_browsertest.cc
@@ -47,7 +47,7 @@
 
 IN_PROC_BROWSER_TEST_F(AppServicePromiseAppShelfContextMenuBrowserTest,
                        MenuOnlyHasPin) {
-  apps::PackageId package_id(apps::AppType::kArc, "com.example.test");
+  apps::PackageId package_id(apps::PackageType::kArc, "com.example.test");
   AddTestPromiseApp(package_id);
 
   ash::ShelfModel* shelf_model = ash::ShelfModel::Get();
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
index 534f253..ff271b3 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
@@ -6131,7 +6131,7 @@
 TEST_F(ChromeShelfControllerPromiseAppsTest, PromiseAppUpdatesShelfItem) {
   // Register a promise app.
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, "com.example.test");
+      apps::PackageId(apps::PackageType::kArc, "com.example.test");
   apps::PromiseAppPtr promise_app =
       std::make_unique<apps::PromiseApp>(package_id);
   promise_app->status = apps::PromiseStatus::kPending;
@@ -6175,7 +6175,7 @@
        PromiseAppUpdatesCorrectShelfItem) {
   // Register the main promise app that we will check the updates for.
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, "main.package.for.test");
+      apps::PackageId(apps::PackageType::kArc, "main.package.for.test");
   apps::PromiseAppPtr promise_app =
       std::make_unique<apps::PromiseApp>(package_id);
   promise_app->status = apps::PromiseStatus::kPending;
@@ -6185,7 +6185,7 @@
   // Register another promise app that will have a shelf item but which we do
   // not expect updates for.
   const apps::PackageId other_package_id =
-      apps::PackageId(apps::AppType::kArc, "other.package");
+      apps::PackageId(apps::PackageType::kArc, "other.package");
   apps::PromiseAppPtr other_promise_app =
       std::make_unique<apps::PromiseApp>(other_package_id);
   other_promise_app->status = apps::PromiseStatus::kPending;
@@ -6225,7 +6225,7 @@
        ShelfItemFetchesAndAppliesEffectsToIcon) {
   // Register the main promise app that we will check the updates for.
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, "com.example.test");
+      apps::PackageId(apps::PackageType::kArc, "com.example.test");
   apps::PromiseAppPtr promise_app =
       std::make_unique<apps::PromiseApp>(package_id);
   promise_app->status = apps::PromiseStatus::kPending;
@@ -6262,7 +6262,7 @@
   // Register a promise app.
   apps::AppType app_type = apps::AppType::kArc;
   std::string identifier = "test.com.example";
-  apps::PackageId package_id(app_type, identifier);
+  apps::PackageId package_id(apps::PackageType::kArc, identifier);
   apps::PromiseAppPtr promise_app =
       std::make_unique<apps::PromiseApp>(package_id);
   promise_app->progress = 0.9;
@@ -6301,7 +6301,7 @@
 TEST_F(ChromeShelfControllerPromiseAppsTest, PinnedPromiseAppShelfItemType) {
   // Register a promise app.
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, "com.example.test");
+      apps::PackageId(apps::PackageType::kArc, "com.example.test");
   apps::PromiseAppPtr promise_app =
       std::make_unique<apps::PromiseApp>(package_id);
   promise_app->should_show = true;
@@ -6321,7 +6321,7 @@
 TEST_F(ChromeShelfControllerPromiseAppsTest,
        PromiseAppGetsPinnedByMatchingSyncData) {
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, "com.example.test");
+      apps::PackageId(apps::PackageType::kArc, "com.example.test");
 
   // Add entry in sync data that has a matching PackageId with the promise app.
   SendPinChanges(syncer::SyncChangeList(), true);
@@ -6357,7 +6357,7 @@
 TEST_F(ChromeShelfControllerPromiseAppsTest,
        PromiseAppNotPinnedByMatchingSyncDataIfNotReadyToBeShown) {
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, "com.example.test");
+      apps::PackageId(apps::PackageType::kArc, "com.example.test");
 
   // Add entry in sync data that has a matching PackageId with our promise app.
   SendPinChanges(syncer::SyncChangeList(), true);
@@ -6399,7 +6399,7 @@
 TEST_F(ChromeShelfControllerPromiseAppsTest, SyncDataCreatesCorrectShelfItem) {
   std::string package_name = "com.example.test";
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, package_name);
+      apps::PackageId(apps::PackageType::kArc, package_name);
   std::string app_id = "bijolhehmgkcaahdbconomepmenmeomc";
 
   // Add entry in sync data that has a matching PackageId with our promise app.
@@ -6479,7 +6479,7 @@
       apps::PromiseAppLifecycleEvent::kCreatedInShelf, 0);
 
   const apps::PackageId package_id =
-      apps::PackageId(apps::AppType::kArc, "com.example.test");
+      apps::PackageId(apps::PackageType::kArc, "com.example.test");
   apps::PromiseAppPtr promise_app =
       std::make_unique<apps::PromiseApp>(package_id);
   promise_app->should_show = true;
diff --git a/chrome/browser/ui/autofill/address_bubbles_controller_interactive_uitest.cc b/chrome/browser/ui/autofill/address_bubbles_controller_interactive_uitest.cc
index 77a716a..275833c2 100644
--- a/chrome/browser/ui/autofill/address_bubbles_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/autofill/address_bubbles_controller_interactive_uitest.cc
@@ -100,13 +100,12 @@
                  /*screenshot_name=*/"save_popup", /*baseline_cl=*/"4535916"),
       PressButton(SaveAddressProfileView::kEditButtonViewId),
 
-      // The editor popup resides in a different context on MacOS.
-      InAnyContext(
-          Steps(WaitForShow(EditAddressProfileView::kTopViewId),
-                Screenshot(EditAddressProfileView::kTopViewId,
-                           /*screenshot_name=*/"edit_popup",
-                           /*baseline_cl=*/"4535916"),
-                PressButton(views::DialogClientView::kCancelButtonElementId))),
+      WaitForShow(EditAddressProfileView::kTopViewId),
+      Screenshot(EditAddressProfileView::kTopViewId,
+                 /*screenshot_name=*/"edit_popup",
+                 /*baseline_cl=*/"4535916"),
+      PressButton(views::DialogClientView::kCancelButtonElementId),
+      WaitForHide(EditAddressProfileView::kTopViewId), FlushEvents(),
 
       WaitForShow(SaveAddressProfileView::kTopViewId),
       PressButton(views::DialogClientView::kOkButtonElementId),
@@ -121,10 +120,9 @@
                               kSuppressedScreenshotError),
       ShowInitBubble(), PressButton(SaveAddressProfileView::kEditButtonViewId),
 
-      InAnyContext(Steps(
-          WaitForShow(EditAddressProfileView::kTopViewId),
-          PressButton(views::DialogClientView::kOkButtonElementId),
-          WaitForHide(EditAddressProfileView::kTopViewId), FlushEvents())),
+      WaitForShow(EditAddressProfileView::kTopViewId),
+      PressButton(views::DialogClientView::kOkButtonElementId),
+      WaitForHide(EditAddressProfileView::kTopViewId), FlushEvents(),
 
       EnsureClosedWithDecision(
           AutofillClient::AddressPromptUserDecision::kEditAccepted));
@@ -182,13 +180,12 @@
                  /*baseline_cl=*/"4535916"),
       PressButton(UpdateAddressProfileView::kEditButtonViewId),
 
-      // The editor popup resides in a different context on MacOS.
-      InAnyContext(
-          Steps(WaitForShow(EditAddressProfileView::kTopViewId),
-                Screenshot(EditAddressProfileView::kTopViewId,
-                           /*screenshot_name=*/"edit_popup",
-                           /*baseline_cl=*/"4535916"),
-                PressButton(views::DialogClientView::kCancelButtonElementId))),
+      WaitForShow(EditAddressProfileView::kTopViewId),
+      Screenshot(EditAddressProfileView::kTopViewId,
+                 /*screenshot_name=*/"edit_popup",
+                 /*baseline_cl=*/"4535916"),
+      PressButton(views::DialogClientView::kCancelButtonElementId),
+      WaitForHide(EditAddressProfileView::kTopViewId), FlushEvents(),
 
       WaitForShow(UpdateAddressProfileView::kTopViewId),
       PressButton(views::DialogClientView::kOkButtonElementId),
@@ -221,13 +218,12 @@
                  /*baseline_cl=*/"4535916"),
       PressButton(UpdateAddressProfileView::kEditButtonViewId),
 
-      // The editor popup resides in a different context on MacOS.
-      InAnyContext(
-          Steps(WaitForShow(EditAddressProfileView::kTopViewId),
-                Screenshot(EditAddressProfileView::kTopViewId,
-                           /*screenshot_name=*/"edit_popup",
-                           /*baseline_cl=*/"4535916"),
-                PressButton(views::DialogClientView::kCancelButtonElementId))),
+      WaitForShow(EditAddressProfileView::kTopViewId),
+      Screenshot(EditAddressProfileView::kTopViewId,
+                 /*screenshot_name=*/"edit_popup",
+                 /*baseline_cl=*/"4535916"),
+      PressButton(views::DialogClientView::kCancelButtonElementId),
+      WaitForHide(EditAddressProfileView::kTopViewId), FlushEvents(),
 
       WaitForShow(UpdateAddressProfileView::kTopViewId),
       PressButton(views::DialogClientView::kOkButtonElementId),
@@ -265,13 +261,12 @@
       Screenshot(SaveAddressProfileView::kTopViewId, "save_popup", "4535916"),
       PressButton(SaveAddressProfileView::kEditButtonViewId),
 
-      // The editor popup resides in a different context on MacOS.
-      InAnyContext(
-          Steps(WaitForShow(EditAddressProfileView::kTopViewId),
-                Screenshot(EditAddressProfileView::kTopViewId,
-                           /*screenshot_name=*/"edit_popup",
-                           /*baseline_cl=*/"4535916"),
-                PressButton(views::DialogClientView::kCancelButtonElementId))),
+      WaitForShow(EditAddressProfileView::kTopViewId),
+      Screenshot(EditAddressProfileView::kTopViewId,
+                 /*screenshot_name=*/"edit_popup",
+                 /*baseline_cl=*/"4535916"),
+      PressButton(views::DialogClientView::kCancelButtonElementId),
+      WaitForHide(EditAddressProfileView::kTopViewId), FlushEvents(),
 
       WaitForShow(SaveAddressProfileView::kTopViewId),
       PressButton(views::DialogClientView::kOkButtonElementId),
@@ -299,24 +294,16 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AddNewAddressProfileTest, EditorCancel) {
-  RunTestSequence(
-      ShowInitBubble(),
-      PressButton(views::DialogClientView::kOkButtonElementId),
-      // The editor popup resides in a different context on MacOS.
-      InAnyContext(
-          Steps(WaitForShow(EditAddressProfileView::kTopViewId),
-                PressButton(views::DialogClientView::kCancelButtonElementId))),
-
-      WaitForShow(AddNewAddressBubbleView::kTopViewId));
+  RunTestSequence(ShowInitBubble(),
+                  PressButton(views::DialogClientView::kOkButtonElementId),
+                  WaitForShow(EditAddressProfileView::kTopViewId),
+                  PressButton(views::DialogClientView::kCancelButtonElementId),
+                  WaitForHide(EditAddressProfileView::kTopViewId),
+                  FlushEvents(),
+                  WaitForShow(AddNewAddressBubbleView::kTopViewId));
 }
 
-// TODO: crbug/325440757 - Consistently fails on Windows.
-#if BUILDFLAG(IS_WIN)
-#define MAYBE_AddAddressAccept DISABLED_AddAddressAccept
-#else
-#define MAYBE_AddAddressAccept AddAddressAccept
-#endif
-IN_PROC_BROWSER_TEST_F(AddNewAddressProfileTest, MAYBE_AddAddressAccept) {
+IN_PROC_BROWSER_TEST_F(AddNewAddressProfileTest, AddAddressAccept) {
   RunTestSequence(
       ShowInitBubble(),
       SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue,
@@ -326,14 +313,12 @@
                  /*baseline_cl=*/"5358737"),
       PressButton(views::DialogClientView::kOkButtonElementId),
 
-      // The editor popup resides in a different context on MacOS.
-      InAnyContext(Steps(
-          WaitForShow(EditAddressProfileView::kTopViewId),
-          Screenshot(EditAddressProfileView::kTopViewId,
-                     /*screenshot_name=*/"edit_popup",
-                     /*baseline_cl=*/"5358737"),
-          PressButton(views::DialogClientView::kOkButtonElementId),
-          WaitForHide(EditAddressProfileView::kTopViewId), FlushEvents())),
+      WaitForShow(EditAddressProfileView::kTopViewId),
+      Screenshot(EditAddressProfileView::kTopViewId,
+                 /*screenshot_name=*/"edit_popup",
+                 /*baseline_cl=*/"5358737"),
+      PressButton(views::DialogClientView::kOkButtonElementId),
+      WaitForHide(EditAddressProfileView::kTopViewId), FlushEvents(),
 
       EnsureClosedWithDecision(
           AutofillClient::AddressPromptUserDecision::kEditAccepted));
diff --git a/chrome/browser/ui/bookmarks/bookmark_context_menu_controller_unittest.cc b/chrome/browser/ui/bookmarks/bookmark_context_menu_controller_unittest.cc
index 19cdeb1..045f146 100644
--- a/chrome/browser/ui/bookmarks/bookmark_context_menu_controller_unittest.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_context_menu_controller_unittest.cc
@@ -43,8 +43,7 @@
 class BookmarkContextMenuControllerTest : public testing::Test {
  public:
   BookmarkContextMenuControllerTest() : model_(nullptr) {
-    feature_list_.InitWithFeatures(
-        {features::kTabGroupsSave, tab_groups::kTabGroupsSaveUIUpdate}, {});
+    feature_list_.InitWithFeatures({tab_groups::kTabGroupsSaveUIUpdate}, {});
   }
 
   void SetUp() override {
diff --git a/chrome/browser/ui/bookmarks/bookmark_utils.cc b/chrome/browser/ui/bookmarks/bookmark_utils.cc
index a8e3c47..98849daa 100644
--- a/chrome/browser/ui/bookmarks/bookmark_utils.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_utils.cc
@@ -160,8 +160,7 @@
 }
 
 bool IsSavedTabGroupsEnabled(Profile* profile) {
-  return base::FeatureList::IsEnabled(features::kTabGroupsSave) &&
-         profile->IsRegularProfile();
+  return profile->IsRegularProfile();
 }
 
 bool ShouldShowAppsShortcutInBookmarkBar(Profile* profile) {
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index 7732245..27a366d 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -1303,10 +1303,7 @@
               ->visual_data();
       const tab_groups::SavedTabGroupKeyedService* const
           saved_tab_group_keyed_service =
-              base::FeatureList::IsEnabled(features::kTabGroupsSave)
-                  ? tab_groups::SavedTabGroupServiceFactory::GetForProfile(
-                        profile_)
-                  : nullptr;
+              tab_groups::SavedTabGroupServiceFactory::GetForProfile(profile_);
       std::optional<std::string> saved_guid;
 
       if (saved_tab_group_keyed_service) {
diff --git a/chrome/browser/ui/browser_tab_strip_model_delegate.cc b/chrome/browser/ui/browser_tab_strip_model_delegate.cc
index 3924d37..18dc477 100644
--- a/chrome/browser/ui/browser_tab_strip_model_delegate.cc
+++ b/chrome/browser/ui/browser_tab_strip_model_delegate.cc
@@ -200,18 +200,13 @@
         BrowserLiveTabContext::FindContextWithGroup(group, browser_->profile()),
         group);
 
-    const bool saved_tab_groups_enabled =
-        base::FeatureList::IsEnabled(features::kTabGroupsSave) ||
-        tab_groups::IsTabGroupsSaveV2Enabled();
-    if (saved_tab_groups_enabled) {
-      tab_groups::SavedTabGroupKeyedService* saved_tab_group_service =
-          tab_groups::SavedTabGroupServiceFactory::GetForProfile(
-              browser_->profile());
-      CHECK(saved_tab_group_service);
+    tab_groups::SavedTabGroupKeyedService* saved_tab_group_service =
+        tab_groups::SavedTabGroupServiceFactory::GetForProfile(
+            browser_->profile());
+    CHECK(saved_tab_group_service);
 
-      if (saved_tab_group_service->model()->Contains(group)) {
-        saved_tab_group_service->DisconnectLocalTabGroup(group);
-      }
+    if (saved_tab_group_service->model()->Contains(group)) {
+      saved_tab_group_service->DisconnectLocalTabGroup(group);
     }
   }
 }
diff --git a/chrome/browser/ui/extensions/extension_action_test_helper.h b/chrome/browser/ui/extensions/extension_action_test_helper.h
index c20419b..609cc39 100644
--- a/chrome/browser/ui/extensions/extension_action_test_helper.h
+++ b/chrome/browser/ui/extensions/extension_action_test_helper.h
@@ -42,11 +42,6 @@
   // Returns the number of browser action buttons in the window toolbar.
   virtual int NumberOfBrowserActions() = 0;
 
-  // Returns the number of browser action currently visible. Note that a correct
-  // result may require a UI layout. Ensure the UI layout is up-to-date (e.g. by
-  // calling InProcessBrowserTest::RunScheduledLayouts()) for a browser test.
-  virtual int VisibleBrowserActions() = 0;
-
   // Returns true if there is an action for the given `id`.
   virtual bool HasAction(const extensions::ExtensionId& id) = 0;
 
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.cc b/chrome/browser/ui/lens/lens_overlay_controller.cc
index 1f49f61..a678d9a 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller.cc
@@ -311,6 +311,11 @@
   }
 }
 
+void LensOverlayController::SendObjects(
+    std::vector<lens::mojom::OverlayObjectPtr> objects) {
+  page_->ObjectsReceived(std::move(objects));
+}
+
 void LensOverlayController::SendText(lens::mojom::TextPtr text) {
   page_->TextReceived(std::move(text));
 }
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.h b/chrome/browser/ui/lens/lens_overlay_controller.h
index c2d4eed..20c098d 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.h
+++ b/chrome/browser/ui/lens/lens_overlay_controller.h
@@ -161,6 +161,9 @@
   // Send text data to the WebUI.
   void SendText(lens::mojom::TextPtr text);
 
+  // Send overlay object data to the WebUI.
+  void SendObjects(std::vector<lens::mojom::OverlayObjectPtr> objects);
+
   // Returns true if the overlay is open and covering the current active tab.
   bool IsOverlayShowing();
 
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index 1d5b4e8f..6a12c45 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -2623,10 +2623,6 @@
 
 void TabStripModel::DisconnectSavedTabGroups(
     const std::vector<int>& indices) const {
-  if (!base::FeatureList::IsEnabled(features::kTabGroupsSave)) {
-    return;
-  }
-
   tab_groups::SavedTabGroupKeyedService* const keyed_service =
       tab_groups::SavedTabGroupServiceFactory::GetForProfile(profile_);
   const tab_groups::SavedTabGroupModel* const stg_model =
diff --git a/chrome/browser/ui/toolbar/chrome_labs/chrome_labs_model.cc b/chrome/browser/ui/toolbar/chrome_labs/chrome_labs_model.cc
index abad8da7..0a683b53 100644
--- a/chrome/browser/ui/toolbar/chrome_labs/chrome_labs_model.cc
+++ b/chrome/browser/ui/toolbar/chrome_labs/chrome_labs_model.cc
@@ -42,13 +42,6 @@
   static const base::NoDestructor<std::vector<LabInfo>> lab_info_([]() {
     std::vector<LabInfo> lab_info;
 
-    // Tab Groups Save.
-    lab_info.emplace_back(
-        flag_descriptions::kTabGroupsSaveId,
-        l10n_util::GetStringUTF16(IDS_TAB_GROUPS_SAVE_EXPERIMENT_NAME),
-        l10n_util::GetStringUTF16(IDS_TAB_GROUPS_SAVE_DESCRIPTION),
-        "tab-groups-save", version_info::Channel::BETA);
-
     // ChromeRefresh2023.
     std::vector<std::u16string> chrome_refresh_variation_descriptions = {
         l10n_util::GetStringUTF16(IDS_CHROMEREFRESH2023_WITHOUT_OMNIBOX)};
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index b33094e..24f09540 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -281,10 +281,6 @@
              "TabGroupsCollapseFreezing",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Enables users to explicitly save and recall tab groups.
-// https://crbug.com/1223929
-BASE_FEATURE(kTabGroupsSave, "TabGroupsSave", base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enables preview images in tab-hover cards.
 // https://crbug.com/928954
 BASE_FEATURE(kTabHoverCardImages,
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h
index 37752f25..729e16a 100644
--- a/chrome/browser/ui/ui_features.h
+++ b/chrome/browser/ui/ui_features.h
@@ -155,8 +155,6 @@
 
 BASE_DECLARE_FEATURE(kTabGroupsCollapseFreezing);
 
-BASE_DECLARE_FEATURE(kTabGroupsSave);
-
 BASE_DECLARE_FEATURE(kTabHoverCardImages);
 
 // These parameters control how long the hover card system waits before
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view_unittest.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view_unittest.cc
index 6eee3255..1b60b5f5 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view_unittest.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view_unittest.cc
@@ -63,8 +63,6 @@
 class BookmarkBarViewBaseTest : public ChromeViewsTestBase {
  public:
   BookmarkBarViewBaseTest() {
-    feature_list_.InitAndEnableFeature(features::kTabGroupsSave);
-
     TestingProfile::Builder profile_builder;
     profile_builder.AddTestingFactory(
         search_engines::SearchEngineChoiceServiceFactory::GetInstance(),
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 22a53fb..2fefb55 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
@@ -199,7 +199,6 @@
   // When the #tab-groups-saved feature flag is turned on and profile is
   // regular, `SavedTabGroupBar` is instantiated. If the #tab-groups-saved
   // feature flag is turned off, there is no SavedTabGroupModel.
-  DCHECK(base::FeatureList::IsEnabled(features::kTabGroupsSave));
   DCHECK(browser_->profile()->IsRegularProfile());
   DCHECK(saved_tab_group_model_);
   SetAccessibilityProperties(
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar_unittest.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar_unittest.cc
index a9ef376..55f2520 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar_unittest.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar_unittest.cc
@@ -60,11 +60,9 @@
   SavedTabGroupBarUnitTest()
       : saved_tab_group_model_(std::make_unique<SavedTabGroupModel>()) {
     if (IsV2UIEnabled()) {
-      feature_list_.InitWithFeatures(
-          {features::kTabGroupsSave, tab_groups::kTabGroupsSaveUIUpdate}, {});
+      feature_list_.InitWithFeatures({tab_groups::kTabGroupsSaveUIUpdate}, {});
     } else {
-      feature_list_.InitWithFeatures({features::kTabGroupsSave},
-                                     {tab_groups::kTabGroupsSaveUIUpdate});
+      feature_list_.InitWithFeatures({}, {tab_groups::kTabGroupsSaveUIUpdate});
     }
   }
 
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_interactive_uitest.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_interactive_uitest.cc
index cccae76..91f6189 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_interactive_uitest.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_interactive_uitest.cc
@@ -69,10 +69,10 @@
   void SetUp() override {
     if (IsV2UIEnabled()) {
       scoped_feature_list_.InitWithFeatures(
-          {features::kTabGroupsSave, tab_groups::kTabGroupsSaveUIUpdate}, {});
+          {tab_groups::kTabGroupsSaveUIUpdate}, {});
     } else {
       scoped_feature_list_.InitWithFeatures(
-          {features::kTabGroupsSave}, {tab_groups::kTabGroupsSaveUIUpdate});
+          {}, {tab_groups::kTabGroupsSaveUIUpdate});
     }
     InteractiveBrowserTest::SetUp();
   }
diff --git a/chrome/browser/ui/views/controls/site_icon_text_and_origin_view.cc b/chrome/browser/ui/views/controls/site_icon_text_and_origin_view.cc
new file mode 100644
index 0000000..4fe2f5af
--- /dev/null
+++ b/chrome/browser/ui/views/controls/site_icon_text_and_origin_view.cc
@@ -0,0 +1,139 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/controls/site_icon_text_and_origin_view.h"
+
+#include <memory>
+#include <string>
+
+#include "base/functional/callback_helpers.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/browser/ui/views/web_apps/web_app_views_utils.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/constrained_window/constrained_window_views.h"
+#include "components/web_modal/web_contents_modal_dialog_manager.h"
+#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/layout/layout_provider.h"
+#include "ui/views/layout/layout_types.h"
+#include "ui/views/layout/table_layout.h"
+#include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
+#include "url/gurl.h"
+
+namespace {
+std::u16string NormalizeSuggestedTitle(std::u16string title) {
+  if (base::StartsWith(title, u"https://")) {
+    title = title.substr(8);
+  }
+  if (base::StartsWith(title, u"http://")) {
+    title = title.substr(7);
+  }
+  return title;
+}
+
+std::u16string GetTrimmedTitle(std::u16string title) {
+  base::TrimWhitespace(title, base::TRIM_ALL, &title);
+  return title;
+}
+}  // namespace
+
+SiteIconTextAndOriginView::SiteIconTextAndOriginView(
+    const gfx::ImageSkia& icon,
+    std::u16string initial_title,
+    std::u16string accessible_title,
+    const GURL& url,
+    content::WebContents* web_contents,
+    base::RepeatingCallback<void(const std::u16string&)> text_tracker_callback)
+    : web_contents_(web_contents),
+      text_tracker_callback_(std::move(text_tracker_callback)) {
+  const ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
+
+  const int textfield_width = 320;
+  auto* layout = SetLayoutManager(std::make_unique<views::TableLayout>());
+  layout
+      ->AddColumn(views::LayoutAlignment::kStretch,
+                  views::LayoutAlignment::kCenter,
+                  views::TableLayout::kFixedSize,
+                  views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+      .AddPaddingColumn(views::TableLayout::kFixedSize,
+                        layout_provider->GetDistanceMetric(
+                            views::DISTANCE_RELATED_CONTROL_HORIZONTAL))
+      .AddColumn(views::LayoutAlignment::kStretch,
+                 views::LayoutAlignment::kCenter,
+                 views::TableLayout::kFixedSize,
+                 views::TableLayout::ColumnSize::kFixed, textfield_width, 0)
+      .AddRows(1, views::TableLayout::kFixedSize)
+      .AddPaddingRow(views::TableLayout::kFixedSize,
+                     layout_provider->GetDistanceMetric(
+                         views::DISTANCE_RELATED_CONTROL_VERTICAL))
+      .AddRows(1, views::TableLayout::kFixedSize);
+
+  auto icon_view = std::make_unique<views::ImageView>();
+  icon_view->SetImage(ui::ImageModel::FromImageSkia(icon));
+  AddChildView(icon_view.release());
+
+  std::u16string current_title =
+      NormalizeSuggestedTitle(GetTrimmedTitle(initial_title));
+  text_tracker_callback_.Run(current_title);
+
+  // TODO(dibyapal): Update the SetAccessibleName() to use the correct one for
+  // Create Shortcuts. Maybe pass that as an input.
+  AddChildView(views::Builder<views::Textfield>()
+                   .CopyAddressTo(&title_field_)
+                   .SetText(current_title)
+                   .SetAccessibleName(accessible_title)
+                   .SetController(this)
+                   .Build());
+
+  // Skip the first column in the 2nd row, that is the area below the icon and
+  // should stay empty.
+  AddChildView(views::Builder<views::View>().Build());
+
+  AddChildView(web_app::CreateOriginLabelFromStartUrl(url,
+                                                      /*is_primary_text=*/false)
+                   .release());
+  title_field_->SelectAll(true);
+}
+
+SiteIconTextAndOriginView::~SiteIconTextAndOriginView() = default;
+
+void SiteIconTextAndOriginView::ContentsChanged(
+    views::Textfield* sender,
+    const std::u16string& new_contents) {
+  CHECK_EQ(sender, title_field_);
+  text_tracker_callback_.Run(GetTrimmedTitle(new_contents));
+
+  // TODO(crbug.com/328588659): This shouldn't be needed but we need to undo
+  // any position changes that are currently incorrectly caused by a
+  // SizeToContents() call, leading to the dialog being anchored off screen
+  // from the Chrome window.
+  auto* const modal_dialog_manager =
+      web_modal::WebContentsModalDialogManager::FromWebContents(web_contents_);
+  if (!modal_dialog_manager) {
+    return;
+  }
+
+  if (!modal_dialog_manager->delegate()) {
+    return;
+  }
+
+  auto* const modal_dialog_host =
+      modal_dialog_manager->delegate()->GetWebContentsModalDialogHost();
+  if (!modal_dialog_host) {
+    return;
+  }
+
+  constrained_window::UpdateWebContentsModalDialogPosition(GetWidget(),
+                                                           modal_dialog_host);
+}
+
+BEGIN_METADATA(SiteIconTextAndOriginView)
+END_METADATA
diff --git a/chrome/browser/ui/views/controls/site_icon_text_and_origin_view.h b/chrome/browser/ui/views/controls/site_icon_text_and_origin_view.h
new file mode 100644
index 0000000..230ff5a4
--- /dev/null
+++ b/chrome/browser/ui/views/controls/site_icon_text_and_origin_view.h
@@ -0,0 +1,61 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_CONTROLS_SITE_ICON_TEXT_AND_ORIGIN_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_CONTROLS_SITE_ICON_TEXT_AND_ORIGIN_VIEW_H_
+
+#include <string>
+
+#include "base/functional/callback_helpers.h"
+#include "base/memory/raw_ptr.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/view.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace gfx {
+class ImageSkia;
+}  // namespace gfx
+
+// This is a simple reusable view that has an |icon| on the left with an
+// empty text field on the right, filled with the |title|. The |url|
+// appears under the text field. Used by the DIY web app install dialog, where
+// the name of the app is configurable from the view.
+// *------------------------------------------------*
+// | Image | Text Field                             |
+// |________________________________________________|
+// |         Url                                    |
+// |________________________________________________|
+// *-------------------------------------------------*
+// Based on current usages of the dialog, an empty text field will disable the
+// Ok button, whose behavior is handled here.
+class SiteIconTextAndOriginView : public views::View,
+                                  public views::TextfieldController {
+  METADATA_HEADER(SiteIconTextAndOriginView, views::View)
+
+ public:
+  SiteIconTextAndOriginView(const gfx::ImageSkia& icon,
+                            std::u16string initial_title,
+                            std::u16string accessible_title,
+                            const GURL& url,
+                            content::WebContents* web_contents,
+                            base::RepeatingCallback<void(const std::u16string&)>
+                                text_tracker_callback);
+
+  ~SiteIconTextAndOriginView() override;
+
+ protected:
+  // views::TextfieldController override
+  void ContentsChanged(views::Textfield* sender,
+                       const std::u16string& new_contents) override;
+
+ private:
+  raw_ptr<views::Textfield> title_field_ = nullptr;
+  raw_ptr<content::WebContents> web_contents_ = nullptr;
+  base::RepeatingCallback<void(const std::u16string&)> text_tracker_callback_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_CONTROLS_SITE_ICON_TEXT_AND_ORIGIN_VIEW_H_
diff --git a/chrome/browser/ui/views/editor_menu/utils/pre_target_handler.cc b/chrome/browser/ui/views/editor_menu/utils/pre_target_handler.cc
index eccb4d8..8aade30 100644
--- a/chrome/browser/ui/views/editor_menu/utils/pre_target_handler.cc
+++ b/chrome/browser/ui/views/editor_menu/utils/pre_target_handler.cc
@@ -237,7 +237,7 @@
 
       // Focus |view_| if compatible key-event should transfer the selection to
       // it from within the menu.
-      if (view_should_gain_focus) {
+      if (view_should_gain_focus && card_type_ != CardType::kEditorMenu) {
         // Track currently focused view to restore back to later and send focus
         // to |view_|.
         external_focus_tracker_->SetFocusManager(focus_manager);
@@ -246,12 +246,6 @@
 
         active_menu = views::MenuController::GetActiveInstance();
 
-        // When the Editor Menu requests focus, the context menu will
-        // eventually be dismissed. In this case, we skip reopening sub-mdenu.
-        if (card_type_ == CardType::kEditorMenu) {
-          return;
-        }
-
         // Reopen the sub-menu owned by |parent| to clear the currently selected
         // boundary menu-item.
         if (parent && active_menu) {
@@ -261,6 +255,13 @@
 
       return;
     }
+    case ui::VKEY_TAB: {
+      if (card_type_ == CardType::kEditorMenu && !view_has_pane_focus) {
+        view_.view()->RequestFocus();
+        key_event->StopPropagation();
+      }
+      return;
+    }
     case ui::VKEY_RETURN: {
       if (view_has_pane_focus) {
         auto* button_in_focus = views::Button::AsButton(currently_focused_view);
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_item_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_item_view.cc
index 1cfcccd..2785dd7 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_item_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_item_view.cc
@@ -30,7 +30,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/models/image_model.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/color/color_id.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/vector_icon_types.h"
@@ -171,15 +170,6 @@
   return button_text;
 }
 
-const gfx::VectorIcon& GetPinIcon(bool is_pinned) {
-  if (is_pinned) {
-    return features::IsChromeRefresh2023() ? kKeepPinFilledChromeRefreshIcon
-                                           : views::kUnpinIcon;
-  }
-  return features::IsChromeRefresh2023() ? kKeepPinChromeRefreshIcon
-                                         : views::kPinIcon;
-}
-
 views::Builder<HoverButton> GetSitePermissionsButtonBuilder(
     views::Button::PressedCallback callback,
     bool is_enterprise,
@@ -189,6 +179,9 @@
   auto button_builder =
       views::Builder<HoverButton>(
           std::make_unique<HoverButton>(std::move(callback), std::u16string()))
+          .SetTitleTextStyle(views::style::STYLE_BODY_5,
+                             ui::kColorDialogBackground,
+                             kColorExtensionsMenuSecondaryText)
           // Align the main and secondary row text by adding the primary
           // action button's icon size as margin.
           .SetProperty(views::kMarginsKey, gfx::Insets::VH(0, icon_size))
@@ -202,22 +195,15 @@
     button_builder.SetHorizontalAlignment(gfx::ALIGN_LEFT)
         .SetImageModel(views::Button::ButtonState::STATE_NORMAL,
                        ui::ImageModel::FromVectorIcon(
-                           features::IsChromeRefresh2023()
-                               ? vector_icons::kBusinessChromeRefreshIcon
-                               : vector_icons::kBusinessIcon,
+                           vector_icons::kBusinessChromeRefreshIcon,
                            ui::kColorIcon, small_icon_size));
 
   } else {
     // Add right-aligned arrow icon for non-enterprise extensions when the
     // button is not disabled.
     auto arrow_icon = ui::ImageModel::FromVectorIcon(
-        features::IsChromeRefresh2023()
-            ? vector_icons::kSubmenuArrowChromeRefreshIcon
-            : vector_icons::kSubmenuArrowIcon,
-        ui::kColorIcon,
-        features::IsChromeRefresh2023()
-            ? small_icon_size
-            : gfx::GetDefaultSizeOfVectorIcon(vector_icons::kSubmenuArrowIcon));
+        vector_icons::kSubmenuArrowChromeRefreshIcon, ui::kColorIcon,
+        small_icon_size);
 
     button_builder.SetHorizontalAlignment(gfx::ALIGN_RIGHT)
         .SetImageModel(views::Button::ButtonState::STATE_NORMAL, arrow_icon)
@@ -347,6 +333,9 @@
                       std::make_unique<ExtensionsMenuButton>(browser_,
                                                              controller_.get()))
                       .CopyAddressTo(&primary_action_button_)
+                      .SetTitleTextStyle(views::style::STYLE_BODY_3_EMPHASIS,
+                                         ui::kColorDialogBackground,
+                                         kColorExtensionsMenuText)
                       .SetProperty(views::kFlexBehaviorKey,
                                    views::FlexSpecification(
                                        views::MinimumFlexSizeRule::kScaleToZero,
@@ -390,15 +379,6 @@
                   .CopyAddressTo(&site_permissions_button_)))
       .BuildChildren();
 
-  if (features::IsChromeRefresh2023()) {
-    primary_action_button_->SetTitleTextStyle(
-        views::style::STYLE_BODY_3_EMPHASIS, ui::kColorDialogBackground,
-        kColorExtensionsMenuText);
-    site_permissions_button_->SetTitleTextStyle(
-        views::style::STYLE_BODY_5, ui::kColorDialogBackground,
-        kColorExtensionsMenuSecondaryText);
-  }
-
   SetupContextMenuButton();
 
   // By default, the button's accessible description is set to the button's
@@ -421,11 +401,9 @@
     bool is_pinned = model_ && model_->IsActionPinned(controller_->GetId());
     UpdateContextMenuButton(is_pinned);
   } else {
-    SetButtonIconWithColor(
-        context_menu_button_,
-        features::IsChromeRefresh2023() ? kBrowserToolsChromeRefreshIcon
-                                        : kBrowserToolsIcon,
-        kColorExtensionMenuIcon, kColorExtensionMenuIconDisabled);
+    SetButtonIconWithColor(context_menu_button_, kBrowserToolsChromeRefreshIcon,
+                           kColorExtensionMenuIcon,
+                           kColorExtensionMenuIconDisabled);
     if (pin_button_) {
       views::InkDrop::Get(pin_button_)->SetBaseColorId(kColorExtensionMenuIcon);
       bool is_pinned = model_ && model_->IsActionPinned(controller_->GetId());
@@ -489,8 +467,10 @@
   const ui::ColorId disabled_icon_color_id =
       is_pinned ? kColorExtensionMenuPinButtonIconDisabled
                 : kColorExtensionMenuIconDisabled;
-  SetButtonIconWithColor(pin_button_, GetPinIcon(is_pinned), icon_color_id,
-                         disabled_icon_color_id);
+  SetButtonIconWithColor(
+      pin_button_,
+      is_pinned ? kKeepPinFilledChromeRefreshIcon : kKeepPinChromeRefreshIcon,
+      icon_color_id, disabled_icon_color_id);
 }
 
 void ExtensionMenuItemView::UpdateContextMenuButton(bool is_action_pinned) {
@@ -500,20 +480,16 @@
   const int icon_size = ChromeLayoutProvider::Get()->GetDistanceMetric(
       DISTANCE_EXTENSIONS_MENU_BUTTON_ICON_SIZE);
   auto three_dot_icon = ui::ImageModel::FromVectorIcon(
-      features::IsChromeRefresh2023() ? kBrowserToolsChromeRefreshIcon
-                                      : kBrowserToolsIcon,
-      kColorExtensionMenuIcon, icon_size);
+      kBrowserToolsChromeRefreshIcon, kColorExtensionMenuIcon, icon_size);
 
   // Show a pin button for the context menu normal state icon when the action is
   // pinned in the toolbar. All other states should look, and behave, the same.
   context_menu_button_->SetImageModel(
       views::Button::STATE_NORMAL,
-      is_action_pinned
-          ? ui::ImageModel::FromVectorIcon(
-                features::IsChromeRefresh2023() ? kKeepPinChromeRefreshIcon
-                                                : views::kUnpinIcon,
-                kColorExtensionMenuPinButtonIcon, icon_size)
-          : three_dot_icon);
+      is_action_pinned ? ui::ImageModel::FromVectorIcon(
+                             kKeepPinChromeRefreshIcon,
+                             kColorExtensionMenuPinButtonIcon, icon_size)
+                       : three_dot_icon);
   context_menu_button_->SetImageModel(views::Button::STATE_HOVERED,
                                       three_dot_icon);
   context_menu_button_->SetImageModel(views::Button::STATE_PRESSED,
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
index 9828ccd..77d453a 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
@@ -36,7 +36,6 @@
 #include "extensions/test/permissions_manager_waiter.h"
 #include "extensions/test/test_extension_dir.h"
 #include "testing/gmock/include/gmock/gmock.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/gfx/image/image_unittest_util.h"
 #include "ui/views/controls/styled_label.h"
 #include "ui/views/vector_icons.h"
@@ -903,10 +902,9 @@
   auto pin_icon = gfx::Image(gfx::CreateVectorIcon(
       views::kPinIcon,
       color_provider->GetColor(kColorExtensionMenuPinButtonIcon)));
-  auto three_dot_icon = gfx::Image(gfx::CreateVectorIcon(
-      features::IsChromeRefresh2023() ? kBrowserToolsChromeRefreshIcon
-                                      : kBrowserToolsIcon,
-      color_provider->GetColor(kColorExtensionMenuIcon)));
+  auto three_dot_icon = gfx::Image(
+      gfx::CreateVectorIcon(kBrowserToolsChromeRefreshIcon,
+                            color_provider->GetColor(kColorExtensionMenuIcon)));
 
   // Verify context menu button has three dot icon for all button states.
   EXPECT_TRUE(gfx::test::AreImagesEqual(
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_test_util.cc b/chrome/browser/ui/views/extensions/extensions_menu_test_util.cc
index 65f4c3b4..b59937a 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_test_util.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_test_util.cc
@@ -138,15 +138,6 @@
   return extensions_container_->GetNumberOfActionsForTesting();
 }
 
-int ExtensionsMenuTestUtil::VisibleBrowserActions() {
-  int visible_icons = 0;
-  for (const auto& id_and_view : extensions_container_->icons_for_testing()) {
-    if (id_and_view.second->GetVisible())
-      ++visible_icons;
-  }
-  return visible_icons;
-}
-
 bool ExtensionsMenuTestUtil::HasAction(const extensions::ExtensionId& id) {
   return GetMenuItemViewForId(id) != nullptr;
 }
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_test_util.h b/chrome/browser/ui/views/extensions/extensions_menu_test_util.h
index 40cfcd2..633e7cc 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_test_util.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_test_util.h
@@ -29,7 +29,6 @@
 
   // ExtensionActionTestHelper:
   int NumberOfBrowserActions() override;
-  int VisibleBrowserActions() override;
   bool HasAction(const extensions::ExtensionId& id) override;
   void InspectPopup(const extensions::ExtensionId& id) override;
   bool HasIcon(const extensions::ExtensionId& id) override;
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
index 17b8fca..c85e00d1 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
@@ -27,7 +27,6 @@
 #include "third_party/skia/include/core/SkPath.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/color/color_id.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/views/accessibility/view_accessibility.h"
@@ -180,14 +179,12 @@
   footer->SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(
       footer->GetInsets().top(), dialog_insets.left() + icon_spacing)));
   footer->SetImageLabelSpacing(footer->GetImageLabelSpacing() + icon_spacing);
-  footer->SetImageModel(views::Button::STATE_NORMAL,
-                        ui::ImageModel::FromVectorIcon(
-                            features::IsChromeRefresh2023()
-                                ? vector_icons::kSettingsChromeRefreshIcon
-                                : vector_icons::kSettingsIcon,
-                            ui::kColorIcon,
-                            provider->GetDistanceMetric(
-                                DISTANCE_EXTENSIONS_MENU_BUTTON_ICON_SIZE)));
+  footer->SetImageModel(
+      views::Button::STATE_NORMAL,
+      ui::ImageModel::FromVectorIcon(
+          vector_icons::kSettingsChromeRefreshIcon, ui::kColorIcon,
+          provider->GetDistanceMetric(
+              DISTANCE_EXTENSIONS_MENU_BUTTON_ICON_SIZE)));
 
   manage_extensions_button_ = footer.get();
   AddChildView(std::move(footer));
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_button.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_button.cc
index 0a7a61a..06ac47e 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_button.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_button.cc
@@ -20,7 +20,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/pointer/touch_ui_controller.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/gfx/vector_icon_types.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/controls/button/button_controller.h"
@@ -30,11 +29,7 @@
 const gfx::VectorIcon& GetIcon(ExtensionsToolbarButton::State state) {
   switch (state) {
     case ExtensionsToolbarButton::State::kDefault:
-      return (features::IsChromeRefresh2023() ||
-              base::FeatureList::IsEnabled(
-                  extensions_features::kExtensionsMenuAccessControl))
-                 ? vector_icons::kExtensionChromeRefreshIcon
-                 : vector_icons::kExtensionIcon;
+      return vector_icons::kExtensionChromeRefreshIcon;
     case ExtensionsToolbarButton::State::kAllExtensionsBlocked:
       return vector_icons::kExtensionOffIcon;
     case ExtensionsToolbarButton::State::kAnyExtensionHasAccess:
@@ -210,11 +205,7 @@
     return kDefaultTouchableIconSize;
   }
 
-  return features::IsChromeRefresh2023() ||
-                 base::FeatureList::IsEnabled(
-                     extensions_features::kExtensionsMenuAccessControl)
-             ? kDefaultIconSizeChromeRefresh
-             : kDefaultIconSize;
+  return kDefaultIconSizeChromeRefresh;
 }
 
 std::u16string ExtensionsToolbarButton::GetTooltipText(
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index 931d28a..4138f19c 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -48,7 +48,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/models/image_model.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/views/interaction/element_tracker_views.h"
 #include "ui/views/layout/animating_layout_manager.h"
 #include "ui/views/layout/flex_layout.h"
@@ -187,12 +186,8 @@
       break;
   }
 
-  if (features::IsChromeRefresh2023() ||
-      base::FeatureList::IsEnabled(
-          extensions_features::kExtensionsMenuAccessControl)) {
-    GetTargetLayoutManager()->SetDefault(views::kMarginsKey,
-                                         gfx::Insets::VH(0, 2));
-  }
+  GetTargetLayoutManager()->SetDefault(views::kMarginsKey,
+                                       gfx::Insets::VH(0, 2));
 
   UpdateControlsVisibility();
 
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
index d22c12d..22b9f7a 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
@@ -120,7 +120,6 @@
   // Updates the controls visibility.
   void UpdateControlsVisibility();
 
-  const ToolbarIcons& icons_for_testing() const { return icons_; }
   ToolbarActionViewController* popup_owner_for_testing() {
     return popup_owner_;
   }
diff --git a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h
index 05bf95f..7f1771a 100644
--- a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h
+++ b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h
@@ -122,8 +122,8 @@
 
   bool GetShowTitle() const;
 
-  SkColor title_bar_color_;
-  SkColor background_color_;
+  SkColor title_bar_color_ = SK_ColorTRANSPARENT;
+  SkColor background_color_ = SK_ColorTRANSPARENT;
 
   std::u16string last_title_;
   std::u16string last_location_;
diff --git a/chrome/browser/ui/views/location_bar/read_anything_icon_view_interactive_uitest.cc b/chrome/browser/ui/views/location_bar/read_anything_icon_view_interactive_uitest.cc
index e3e082a..8ea49fd2 100644
--- a/chrome/browser/ui/views/location_bar/read_anything_icon_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/location_bar/read_anything_icon_view_interactive_uitest.cc
@@ -66,7 +66,9 @@
 };
 
 // Clicking the icon opens reading mode in the side panel.
-IN_PROC_BROWSER_TEST_F(ReadAnythingIconViewTest, OpensReadingModeOnClick) {
+// TODO(crbug.com/333809887): Re-enable this test
+IN_PROC_BROWSER_TEST_F(ReadAnythingIconViewTest,
+                       DISABLED_OpensReadingModeOnClick) {
   SetNoDelaysForTesting();
   SetActivePageDistillable();
   EXPECT_FALSE(IsReadAnythingEntryShowing(browser()));
@@ -75,7 +77,9 @@
 }
 
 // When reading mode is opened, hides the icon.
-IN_PROC_BROWSER_TEST_F(ReadAnythingIconViewTest, OpenReadingModeHidesIcon) {
+// TODO(crbug.com/333809887): Re-enable this test
+IN_PROC_BROWSER_TEST_F(ReadAnythingIconViewTest,
+                       DISABLED_OpenReadingModeHidesIcon) {
   SetNoDelaysForTesting();
   SetActivePageDistillable();
   PageActionIconView* icon = GetReadAnythingOmniboxIcon();
@@ -86,7 +90,8 @@
 
 // When reading mode is already opened, the icon does not show.
 IN_PROC_BROWSER_TEST_F(ReadAnythingIconViewTest,
-                       IconNotVisibleIfReadingModeOpen) {
+                       // TODO(crbug.com/333809887): Re-enable this test
+                       DISABLED_IconNotVisibleIfReadingModeOpen) {
   SetNoDelaysForTesting();
   ShowReadAnythingSidePanel(browser(),
                             SidePanelOpenTrigger::kReadAnythingOmniboxIcon);
diff --git a/chrome/browser/ui/views/mahi/BUILD.gn b/chrome/browser/ui/views/mahi/BUILD.gn
index 427b5b6..fbe5080 100644
--- a/chrome/browser/ui/views/mahi/BUILD.gn
+++ b/chrome/browser/ui/views/mahi/BUILD.gn
@@ -10,11 +10,11 @@
   sources = [
     "mahi_condensed_menu_view.cc",
     "mahi_condensed_menu_view.h",
+    "mahi_menu_constants.h",
     "mahi_menu_controller.cc",
     "mahi_menu_controller.h",
     "mahi_menu_view.cc",
     "mahi_menu_view.h",
-    "mahi_menu_view_ids.h",
   ]
 
   deps = [
diff --git a/chrome/browser/ui/views/mahi/mahi_condensed_menu_view.cc b/chrome/browser/ui/views/mahi/mahi_condensed_menu_view.cc
index cc5e6ad..cca4c5bd 100644
--- a/chrome/browser/ui/views/mahi/mahi_condensed_menu_view.cc
+++ b/chrome/browser/ui/views/mahi/mahi_condensed_menu_view.cc
@@ -10,8 +10,10 @@
 
 #include "base/functional/bind.h"
 #include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram_functions.h"
 #include "build/branding_buildflags.h"
 #include "chrome/browser/chromeos/mahi/mahi_web_contents_manager.h"
+#include "chrome/browser/ui/views/mahi/mahi_menu_constants.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
 #include "chromeos/ui/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -94,6 +96,9 @@
             .id(),
         /*button_type=*/::mahi::ButtonType::kSummary,
         /*question=*/std::u16string());
+
+    base::UmaHistogramEnumeration(kMahiContextMenuButtonClickHistogram,
+                                  MahiMenuButton::kCondensedMenuButton);
   }
 
   void SetBackgroundHighlighted(bool background_highlighted) {
diff --git a/chrome/browser/ui/views/mahi/mahi_condensed_menu_view_unittest.cc b/chrome/browser/ui/views/mahi/mahi_condensed_menu_view_unittest.cc
index 60c9f1a..879181b 100644
--- a/chrome/browser/ui/views/mahi/mahi_condensed_menu_view_unittest.cc
+++ b/chrome/browser/ui/views/mahi/mahi_condensed_menu_view_unittest.cc
@@ -7,9 +7,11 @@
 #include <memory>
 #include <string>
 
+#include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/chromeos/mahi/mahi_browser_util.h"
 #include "chrome/browser/chromeos/mahi/test/fake_mahi_web_contents_manager.h"
 #include "chrome/browser/chromeos/mahi/test/scoped_mahi_web_contents_manager_for_testing.h"
+#include "chrome/browser/ui/views/mahi/mahi_menu_constants.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/display/screen.h"
@@ -49,6 +51,10 @@
   auto* condensed_menu_view =
       menu_widget->SetContentsView(std::make_unique<MahiCondensedMenuView>());
 
+  base::HistogramTester histogram;
+  histogram.ExpectBucketCount(kMahiContextMenuButtonClickHistogram,
+                              MahiMenuButton::kCondensedMenuButton, 0);
+
   // TODO(b/324647147): Add separate button type for condensed menu.
   EXPECT_CALL(
       mock_mahi_web_contents_manager,
@@ -64,6 +70,9 @@
   event_generator.MoveMouseTo(
       condensed_menu_view->GetBoundsInScreen().CenterPoint());
   event_generator.ClickLeftButton();
+
+  histogram.ExpectBucketCount(kMahiContextMenuButtonClickHistogram,
+                              MahiMenuButton::kCondensedMenuButton, 1);
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/views/mahi/mahi_menu_constants.h b/chrome/browser/ui/views/mahi/mahi_menu_constants.h
new file mode 100644
index 0000000..cb3cfcb7
--- /dev/null
+++ b/chrome/browser/ui/views/mahi/mahi_menu_constants.h
@@ -0,0 +1,39 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_MAHI_MAHI_MENU_CONSTANTS_H_
+#define CHROME_BROWSER_UI_VIEWS_MAHI_MAHI_MENU_CONSTANTS_H_
+
+namespace chromeos::mahi {
+
+// IDs used for the views that compose the Mahi Menu UI.
+// Use these for easy access to the views during the unittests.
+// Note that these IDs are only guaranteed to be unique inside
+// `MahiMenuView`.
+enum ViewID {
+  kSummaryButton = 1,
+  kOutlineButton,
+  kSettingsButton,
+  kTextfield,
+  kSubmitQuestionButton,
+};
+
+// Metrics
+// Contains the types of button existed in Mahi Menu View. Note: this should
+// be kept in sync with `MahiMenuButton` enum in
+// tools/metrics/histograms/metadata/chromeos/enums.xml
+enum class MahiMenuButton {
+  kSummaryButton = 0,
+  kOutlineButton = 1,
+  kSubmitQuestionButton = 2,
+  kCondensedMenuButton = 3,
+  kMaxValue = kCondensedMenuButton,
+};
+
+inline constexpr char kMahiContextMenuButtonClickHistogram[] =
+    "ChromeOS.Mahi.ContextMenuView.ButtonClicked";
+
+}  // namespace chromeos::mahi
+
+#endif  // CHROME_BROWSER_UI_VIEWS_MAHI_MAHI_MENU_CONSTANTS_H_
diff --git a/chrome/browser/ui/views/mahi/mahi_menu_controller.cc b/chrome/browser/ui/views/mahi/mahi_menu_controller.cc
index e4c862f..d8bde87 100644
--- a/chrome/browser/ui/views/mahi/mahi_menu_controller.cc
+++ b/chrome/browser/ui/views/mahi/mahi_menu_controller.cc
@@ -28,8 +28,8 @@
 void MahiMenuController::OnTextAvailable(const gfx::Rect& anchor_bounds,
                                          const std::string& selected_text,
                                          const std::string& surrounding_text) {
-  // TODO(b:330811526): Update this when pref value in lacros is implemented.
-  if (!chromeos::MahiManager::Get()->IsSupportedWithCorrectFeatureKey()) {
+  if (!chromeos::MahiManager::Get()->IsSupportedWithCorrectFeatureKey() ||
+      !::mahi::MahiWebContentsManager::Get()->GetPrefValue()) {
     return;
   }
 
diff --git a/chrome/browser/ui/views/mahi/mahi_menu_controller_unittest.cc b/chrome/browser/ui/views/mahi/mahi_menu_controller_unittest.cc
index c18336c..0bde3bac 100644
--- a/chrome/browser/ui/views/mahi/mahi_menu_controller_unittest.cc
+++ b/chrome/browser/ui/views/mahi/mahi_menu_controller_unittest.cc
@@ -46,6 +46,8 @@
     // Sets the focused page's distillability to true so that it does not block
     // the menu widget's display.
     ChangePageDistillability(true);
+    // Sets the default pref is true for testing.
+    ChangePrefValue(true);
   }
 
   MahiMenuControllerTest(const MahiMenuControllerTest&) = delete;
@@ -60,6 +62,10 @@
         value);
   }
 
+  void ChangePrefValue(bool value) {
+    fake_mahi_web_contents_manager_.SetPrefForTesting(value);
+  }
+
  protected:
   ReadWriteCardsUiController read_write_cards_ui_controller_;
 
@@ -152,6 +158,43 @@
   EXPECT_FALSE(read_write_cards_ui_controller_.GetMahiViewForTest());
 }
 
+// Tests the behavior of the controller when pref state changed.
+TEST_F(MahiMenuControllerTest, PrefChange) {
+  EXPECT_FALSE(menu_controller()->menu_widget_for_test());
+
+  // Menu widget should show when text is displayed as the default is that Mahi
+  // is enabled.
+  menu_controller()->OnTextAvailable(/*anchor_bounds=*/gfx::Rect(),
+                                     /*selected_text=*/"",
+                                     /*surrounding_text=*/"");
+
+  EXPECT_TRUE(menu_controller()->menu_widget_for_test());
+  EXPECT_TRUE(menu_controller()->menu_widget_for_test()->IsVisible());
+  EXPECT_TRUE(views::IsViewClass<MahiMenuView>(
+      menu_controller()->menu_widget_for_test()->GetContentsView()));
+
+  // Menu widget should hide when dismissed.
+  menu_controller()->OnDismiss(/*is_other_command_executed=*/false);
+  EXPECT_FALSE(menu_controller()->menu_widget_for_test());
+
+  // If pref value is false, then menu widget should not be triggered.
+  ChangePrefValue(false);
+  menu_controller()->OnTextAvailable(/*anchor_bounds=*/gfx::Rect(),
+                                     /*selected_text=*/"",
+                                     /*surrounding_text=*/"");
+  EXPECT_FALSE(menu_controller()->menu_widget_for_test());
+
+  // Set pref to true should show the widget again.
+  ChangePrefValue(true);
+  menu_controller()->OnTextAvailable(/*anchor_bounds=*/gfx::Rect(),
+                                     /*selected_text=*/"",
+                                     /*surrounding_text=*/"");
+  EXPECT_TRUE(menu_controller()->menu_widget_for_test());
+  EXPECT_TRUE(menu_controller()->menu_widget_for_test()->IsVisible());
+  EXPECT_TRUE(views::IsViewClass<MahiMenuView>(
+      menu_controller()->menu_widget_for_test()->GetContentsView()));
+}
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 class MahiMenuControllerFeatureKeyTest : public ChromeViewsTestBase {
  public:
diff --git a/chrome/browser/ui/views/mahi/mahi_menu_view.cc b/chrome/browser/ui/views/mahi/mahi_menu_view.cc
index f8c09a2..208bda9 100644
--- a/chrome/browser/ui/views/mahi/mahi_menu_view.cc
+++ b/chrome/browser/ui/views/mahi/mahi_menu_view.cc
@@ -9,12 +9,13 @@
 #include <string>
 
 #include "base/functional/bind.h"
+#include "base/metrics/histogram_functions.h"
 #include "build/branding_buildflags.h"
 #include "chrome/browser/chromeos/mahi/mahi_browser_util.h"
 #include "chrome/browser/chromeos/mahi/mahi_web_contents_manager.h"
 #include "chrome/browser/ui/views/editor_menu/utils/pre_target_handler.h"
 #include "chrome/browser/ui/views/editor_menu/utils/utils.h"
-#include "chrome/browser/ui/views/mahi/mahi_menu_view_ids.h"
+#include "chrome/browser/ui/views/mahi/mahi_menu_constants.h"
 #include "chromeos/components/mahi/public/cpp/mahi_manager.h"
 #include "chromeos/components/mahi/public/cpp/views/experiment_badge.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
@@ -281,6 +282,17 @@
   ::mahi::MahiWebContentsManager::Get()->OnContextMenuClicked(
       display.id(), button_type,
       /*question=*/std::u16string());
+  MahiMenuButton histogram_button_type;
+  if (button_type == ::mahi::ButtonType::kSummary) {
+    histogram_button_type = MahiMenuButton::kSummaryButton;
+  } else {
+    // This function only handles clicks of type 'kSummary' and 'kOutline'.
+    // Other click types are not passed here.
+    CHECK(button_type == ::mahi::ButtonType::kOutline);
+    histogram_button_type = MahiMenuButton::kOutlineButton;
+  }
+  base::UmaHistogramEnumeration(kMahiContextMenuButtonClickHistogram,
+                                histogram_button_type);
 }
 
 void MahiMenuView::OnQuestionSubmitted() {
@@ -289,6 +301,8 @@
   ::mahi::MahiWebContentsManager::Get()->OnContextMenuClicked(
       display.id(), /*button_type=*/::mahi::ButtonType::kQA,
       textfield_->GetText());
+  base::UmaHistogramEnumeration(kMahiContextMenuButtonClickHistogram,
+                                MahiMenuButton::kSubmitQuestionButton);
 }
 
 std::unique_ptr<views::FlexLayoutView> MahiMenuView::CreateInputContainer() {
diff --git a/chrome/browser/ui/views/mahi/mahi_menu_view_ids.h b/chrome/browser/ui/views/mahi/mahi_menu_view_ids.h
deleted file mode 100644
index 5d17ae5..0000000
--- a/chrome/browser/ui/views/mahi/mahi_menu_view_ids.h
+++ /dev/null
@@ -1,24 +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 CHROME_BROWSER_UI_VIEWS_MAHI_MAHI_MENU_VIEW_IDS_H_
-#define CHROME_BROWSER_UI_VIEWS_MAHI_MAHI_MENU_VIEW_IDS_H_
-
-namespace chromeos::mahi {
-
-// IDs used for the views that compose the Mahi Menu UI.
-// Use these for easy access to the views during the unittests.
-// Note that these IDs are only guaranteed to be unique inside
-// `MahiMenuView`.
-enum ViewID {
-  kSummaryButton = 1,
-  kOutlineButton,
-  kSettingsButton,
-  kTextfield,
-  kSubmitQuestionButton,
-};
-
-}  // namespace chromeos::mahi
-
-#endif  // CHROME_BROWSER_UI_VIEWS_MAHI_MAHI_MENU_VIEW_IDS_H_
diff --git a/chrome/browser/ui/views/mahi/mahi_menu_view_unittest.cc b/chrome/browser/ui/views/mahi/mahi_menu_view_unittest.cc
index 74bb8c7..7ecedc3 100644
--- a/chrome/browser/ui/views/mahi/mahi_menu_view_unittest.cc
+++ b/chrome/browser/ui/views/mahi/mahi_menu_view_unittest.cc
@@ -8,12 +8,13 @@
 #include <string>
 
 #include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/chromeos/mahi/mahi_browser_util.h"
 #include "chrome/browser/chromeos/mahi/mahi_web_contents_manager.h"
 #include "chrome/browser/chromeos/mahi/test/fake_mahi_web_contents_manager.h"
 #include "chrome/browser/chromeos/mahi/test/scoped_mahi_web_contents_manager_for_testing.h"
 #include "chrome/browser/ui/views/editor_menu/utils/utils.h"
-#include "chrome/browser/ui/views/mahi/mahi_menu_view_ids.h"
+#include "chrome/browser/ui/views/mahi/mahi_menu_constants.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/display/screen.h"
@@ -97,6 +98,10 @@
                                    ->GetBoundsInScreen()
                                    .CenterPoint());
 
+  base::HistogramTester histogram;
+  histogram.ExpectBucketCount(kMahiContextMenuButtonClickHistogram,
+                              MahiMenuButton::kSummaryButton, 0);
+
   // Make sure that clicking the summary button would trigger the function in
   // `MahiWebContentsManager` with the correct parameters.
   base::RunLoop run_loop;
@@ -115,6 +120,9 @@
 
   event_generator->ClickLeftButton();
   run_loop.Run();
+
+  histogram.ExpectBucketCount(kMahiContextMenuButtonClickHistogram,
+                              MahiMenuButton::kSummaryButton, 1);
 }
 
 // TODO(b/330643995): Remove this test after outlines are shown by default.
@@ -146,6 +154,10 @@
                                    ->GetBoundsInScreen()
                                    .CenterPoint());
 
+  base::HistogramTester histogram;
+  histogram.ExpectBucketCount(kMahiContextMenuButtonClickHistogram,
+                              MahiMenuButton::kOutlineButton, 0);
+
   // Make sure that clicking the summary button would trigger the function in
   // `MahiWebContentsManager` with the correct parameters.
   base::RunLoop run_loop;
@@ -164,6 +176,9 @@
 
   event_generator->ClickLeftButton();
   run_loop.Run();
+
+  histogram.ExpectBucketCount(kMahiContextMenuButtonClickHistogram,
+                              MahiMenuButton::kOutlineButton, 1);
 }
 
 TEST_F(MahiMenuViewTest, SubmitQuestionButtonEnabledAfterTextInput) {
@@ -214,6 +229,10 @@
           ->GetBoundsInScreen()
           .CenterPoint());
 
+  base::HistogramTester histogram;
+  histogram.ExpectBucketCount(kMahiContextMenuButtonClickHistogram,
+                              MahiMenuButton::kSubmitQuestionButton, 0);
+
   // Make sure that clicking the summary button would trigger the function in
   // `MahiWebContentsManager` with the correct parameters.
   base::RunLoop run_loop;
@@ -232,6 +251,9 @@
 
   event_generator->ClickLeftButton();
   run_loop.Run();
+
+  histogram.ExpectBucketCount(kMahiContextMenuButtonClickHistogram,
+                              MahiMenuButton::kSubmitQuestionButton, 1);
 }
 
 TEST_F(MahiMenuViewTest, EmptyQuestionNotSubmitted) {
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
index ab51f66c..5ae5ede0 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
@@ -12,6 +12,7 @@
 #include "base/metrics/field_trial_params.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/accessibility/embedded_a11y_extension_loader.h"
 #include "chrome/browser/language/language_model_manager_factory.h"
@@ -30,6 +31,7 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
 #include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_prefs.h"
 #include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.h"
+#include "chrome/common/extensions/extension_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/language/core/browser/language_model.h"
@@ -38,6 +40,10 @@
 #include "ui/accessibility/accessibility_features.h"
 #include "ui/base/l10n/l10n_util.h"
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chrome/browser/lacros/embedded_a11y_manager_lacros.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
 namespace {
 
 // Get the list of distillable URLs defined by the experiment parameter.
@@ -96,8 +102,7 @@
     BrowserList::GetInstance()->AddObserver(this);
   }
 
-  extension_loader_ = EmbeddedA11yExtensionLoader::GetInstance();
-  extension_loader_->Init();
+  EmbeddedA11yExtensionLoader::GetInstance()->Init();
 }
 
 void ReadAnythingCoordinator::InitModelWithUserPrefs() {
@@ -152,10 +157,7 @@
 }
 
 ReadAnythingCoordinator::~ReadAnythingCoordinator() {
-  if (extension_loader_) {
-    extension_loader_->RemoveA11yHelperExtensionForReadingMode();
-    extension_loader_ = nullptr;
-  }
+  RemoveGDocsHelperExtension();
 
   // Inform observers when |this| is destroyed so they can do their own cleanup.
   for (Observer& obs : observers_) {
@@ -263,7 +265,7 @@
   // TODO(crbug.com/324143642): Handle the installation of a11y helper extension
   // for local side panels.
   if (!features::IsReadAnythingLocalSidePanelEnabled()) {
-    extension_loader_->InstallA11yHelperExtensionForReadingMode();
+    InstallGDocsHelperExtension();
   }
 }
 
@@ -275,7 +277,7 @@
   // TODO(crbug.com/324143642): Handle the uninstallation of a11y helper
   // extension for local side panels.
   if (!features::IsReadAnythingLocalSidePanelEnabled()) {
-    extension_loader_->RemoveA11yHelperExtensionForReadingMode();
+    RemoveGDocsHelperExtension();
   }
 }
 
@@ -413,6 +415,27 @@
   }
 }
 
+void ReadAnythingCoordinator::InstallGDocsHelperExtension() {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  EmbeddedA11yManagerLacros::GetInstance()->SetReadingModeEnabled(true);
+#else
+  EmbeddedA11yExtensionLoader::GetInstance()->InstallExtensionWithId(
+      extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+}
+
+void ReadAnythingCoordinator::RemoveGDocsHelperExtension() {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  EmbeddedA11yManagerLacros::GetInstance()->SetReadingModeEnabled(false);
+#else
+  EmbeddedA11yExtensionLoader::GetInstance()->RemoveExtensionWithId(
+      extension_misc::kReadingModeGDocsHelperExtensionId);
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+}
+
 void ReadAnythingCoordinator::ActivePageNotDistillableForTesting() {
   ActivePageNotDistillable();
 }
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h
index 534bc14..06c2307 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h
@@ -18,7 +18,6 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_entry_observer.h"
 #include "content/public/browser/web_contents_observer.h"
 
-class EmbeddedA11yExtensionLoader;
 class Browser;
 class ReadAnythingController;
 class SidePanelRegistry;
@@ -112,6 +111,9 @@
   void ActivePageNotDistillable();
   bool IsActivePageDistillable() const;
 
+  void InstallGDocsHelperExtension();
+  void RemoveGDocsHelperExtension();
+
   std::string default_language_code_;
   std::unique_ptr<ReadAnythingModel> model_;
   std::unique_ptr<ReadAnythingController> controller_;
@@ -120,8 +122,6 @@
 
   base::ObserverList<Observer> observers_;
 
-  raw_ptr<EmbeddedA11yExtensionLoader> extension_loader_;
-
   bool post_tab_change_delay_complete_ = true;
   base::RetainingOneShotTimer delay_timer_;
 
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc
index 528b14a..58c6cab 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc
@@ -20,9 +20,14 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
+#include "chrome/common/extensions/extension_constants.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/accessibility/accessibility_features.h"
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chrome/browser/lacros/embedded_a11y_manager_lacros.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
 using testing::_;
 
 class MockReadAnythingCoordinatorObserver
@@ -33,13 +38,6 @@
   MOCK_METHOD(void, OnCoordinatorDestroyed, (), (override));
 };
 
-class MockEmbeddedA11yExtensionLoader : public EmbeddedA11yExtensionLoader {
- public:
-  MockEmbeddedA11yExtensionLoader() : EmbeddedA11yExtensionLoader() {}
-  MOCK_METHOD(void, InstallA11yHelperExtensionForReadingMode, (), (override));
-  MOCK_METHOD(void, RemoveA11yHelperExtensionForReadingMode, (), (override));
-};
-
 class ReadAnythingCoordinatorTest : public TestWithBrowserView {
  public:
   void SetUp() override {
@@ -53,10 +51,6 @@
         SidePanelCoordinator::GetGlobalSidePanelRegistry(browser());
     read_anything_coordinator_ =
         ReadAnythingCoordinator::GetOrCreateForBrowser(browser());
-    mock_extension_loader_ =
-        std::make_unique<MockEmbeddedA11yExtensionLoader>();
-    read_anything_coordinator_->extension_loader_ =
-        mock_extension_loader_.get();
 
     AddTab(browser_view()->browser(), GURL("http://foo1.com"));
     browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
@@ -111,7 +105,6 @@
 
   MockReadAnythingCoordinatorObserver coordinator_observer_;
   base::test::ScopedFeatureList scoped_feature_list_;
-  std::unique_ptr<MockEmbeddedA11yExtensionLoader> mock_extension_loader_;
 };
 
 // TODO(crbug.com/1344891): Fix the memory leak on destruction observed on these
@@ -158,19 +151,42 @@
   entry->OnEntryHidden();
 }
 
-TEST_F(ReadAnythingCoordinatorTest, EmbeddedA11yExtensionLoaderCalled) {
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
+TEST_F(ReadAnythingCoordinatorTest,
+       SidePanelShowAndHide_NonLacros_CallEmbeddedA11yExtensionLoader) {
   SidePanelEntry* entry = side_panel_registry_->GetEntryForKey(
       SidePanelEntry::Key(SidePanelEntry::Id::kReadAnything));
+  EXPECT_FALSE(EmbeddedA11yExtensionLoader::GetInstance()->IsExtensionInstalled(
+      extension_misc::kReadingModeGDocsHelperExtensionId));
 
-  EXPECT_CALL(*mock_extension_loader_, InstallA11yHelperExtensionForReadingMode)
-      .Times(1);
   entry->OnEntryShown();
+  EXPECT_TRUE(EmbeddedA11yExtensionLoader::GetInstance()->IsExtensionInstalled(
+      extension_misc::kReadingModeGDocsHelperExtensionId));
 
   // Called once when calling OnEntryHidden and once on destruction.
-  EXPECT_CALL(*mock_extension_loader_, RemoveA11yHelperExtensionForReadingMode)
-      .Times(2);
   entry->OnEntryHidden();
+  EXPECT_FALSE(EmbeddedA11yExtensionLoader::GetInstance()->IsExtensionInstalled(
+      extension_misc::kReadingModeGDocsHelperExtensionId));
 }
+#endif  // !BUILDFLAG(IS_CHROMEOS_LACROS)
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+TEST_F(
+    ReadAnythingCoordinatorTest,
+    SidePanelShowAndHide_Lacros_EmbeddedA11yManagerLacrosUpdateReadingModeState) {
+  SidePanelEntry* entry = side_panel_registry_->GetEntryForKey(
+      SidePanelEntry::Key(SidePanelEntry::Id::kReadAnything));
+  EXPECT_FALSE(
+      EmbeddedA11yManagerLacros::GetInstance()->IsReadingModeEnabled());
+
+  entry->OnEntryShown();
+  EXPECT_TRUE(EmbeddedA11yManagerLacros::GetInstance()->IsReadingModeEnabled());
+
+  entry->OnEntryHidden();
+  EXPECT_FALSE(
+      EmbeddedA11yManagerLacros::GetInstance()->IsReadingModeEnabled());
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
 TEST_F(ReadAnythingCoordinatorTest,
        OnBrowserSetLastActive_SidePanelIsNotVisible) {
diff --git a/chrome/browser/ui/views/side_panel/side_panel.cc b/chrome/browser/ui/views/side_panel/side_panel.cc
index c3a7935..c90238f 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel.cc
@@ -338,7 +338,7 @@
 }
 
 double SidePanel::GetAnimationValue() const {
-  if (lens::features::IsLensOverlayEnabled()) {
+  if (ShouldShowAnimation()) {
     return animation_.GetCurrentValue();
   } else {
     return 1;
@@ -504,8 +504,7 @@
       border_view_->DestroyLayer();
     }
   }
-  if (lens::features::IsLensOverlayEnabled() &&
-      gfx::Animation::ShouldRenderRichAnimation() && !animations_disabled_) {
+  if (ShouldShowAnimation()) {
     if (should_be_open) {
       // If the side panel should remain open but there are views to hide, hide
       // them immediately.
@@ -526,5 +525,10 @@
   }
 }
 
+bool SidePanel::ShouldShowAnimation() const {
+  return lens::features::IsLensOverlayEnabled() &&
+         gfx::Animation::ShouldRenderRichAnimation() && !animations_disabled_;
+}
+
 BEGIN_METADATA(SidePanel)
 END_METADATA
diff --git a/chrome/browser/ui/views/side_panel/side_panel.h b/chrome/browser/ui/views/side_panel/side_panel.h
index 3f4e4c1..cfdeec96 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.h
+++ b/chrome/browser/ui/views/side_panel/side_panel.h
@@ -69,6 +69,7 @@
 
  private:
   void UpdateVisibility();
+  bool ShouldShowAnimation() const;
 
   // views::View:
   void ChildVisibilityChanged(View* child) override;
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index 5933e59a..67f4cb23 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -2822,8 +2822,7 @@
 }
 
 void TabDragController::MaybePauseTrackingSavedTabGroup() {
-  if (!header_drag_ ||
-      !base::FeatureList::IsEnabled(features::kTabGroupsSave)) {
+  if (!header_drag_) {
     return;
   }
 
@@ -2847,7 +2846,6 @@
 
 void TabDragController::MaybeResumeTrackingSavedTabGroup() {
   if (!header_drag_ ||
-      !base::FeatureList::IsEnabled(features::kTabGroupsSave) ||
       !paused_saved_group_id_.has_value()) {
     return;
   }
diff --git a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc
index 0eb80d1..a0d4fbd7 100644
--- a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc
@@ -266,8 +266,7 @@
 
   views::View* save_group_line_container = nullptr;
 
-  if (base::FeatureList::IsEnabled(features::kTabGroupsSave) &&
-      browser_->profile()->IsRegularProfile()) {
+  if (browser_->profile()->IsRegularProfile()) {
     save_group_line_container = AddChildView(std::make_unique<views::View>());
 
     // The `save_group_icon_` is put in differently than the rest because it
@@ -369,8 +368,6 @@
   separator->SetProperty(views::kMarginsKey,
                          gfx::Insets::VH(vertical_spacing, 0));
 
-  // The save_group_line_container is only created if the
-  // feature::kTabGroupsSave is enabled.
   if (save_group_line_container) {
     gfx::Insets save_group_margins = control_insets;
     const int label_height = new_tab_menu_item->GetPreferredSize().height();
@@ -446,10 +443,6 @@
 }
 
 const std::u16string TabGroupEditorBubbleView::GetTextForCloseButton() {
-  if (!base::FeatureList::IsEnabled(features::kTabGroupsSave)) {
-    return l10n_util::GetStringUTF16(IDS_TAB_GROUP_HEADER_CXMENU_CLOSE_GROUP);
-  }
-
   tab_groups::SavedTabGroupKeyedService* const saved_tab_group_service =
       tab_groups::SavedTabGroupServiceFactory::GetForProfile(
           browser_->profile());
@@ -516,8 +509,7 @@
 }
 
 void TabGroupEditorBubbleView::UngroupPressed() {
-  if (base::FeatureList::IsEnabled(features::kTabGroupsSave) &&
-      tab_groups::IsTabGroupsSaveUIUpdateEnabled() &&
+  if (tab_groups::IsTabGroupsSaveUIUpdateEnabled() &&
       save_group_toggle_->GetIsOn()) {
     browser_->tab_group_deletion_dialog_controller()->MaybeShowDialog(
         tab_groups::DeletionDialogController::DialogType::UngroupSingle,
@@ -533,13 +525,12 @@
                                        tab_groups::TabGroupId group) {
   base::RecordAction(
       base::UserMetricsAction("TabGroups_TabGroupBubble_Ungroup"));
-  if (base::FeatureList::IsEnabled(features::kTabGroupsSave)) {
-    tab_groups::SavedTabGroupKeyedService* saved_tab_group_service =
-        tab_groups::SavedTabGroupServiceFactory::GetForProfile(
-            browser->profile());
-    CHECK(saved_tab_group_service);
-    saved_tab_group_service->DisconnectLocalTabGroup(group);
-  }
+
+  tab_groups::SavedTabGroupKeyedService* saved_tab_group_service =
+      tab_groups::SavedTabGroupServiceFactory::GetForProfile(
+          browser->profile());
+  CHECK(saved_tab_group_service);
+  saved_tab_group_service->DisconnectLocalTabGroup(group);
 
   TabStripModel* const model = browser->tab_strip_model();
   const gfx::Range tab_range =
diff --git a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc
index e0f1a61..29bd947 100644
--- a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc
@@ -229,8 +229,8 @@
     : public TabGroupEditorBubbleViewDialogBrowserTest {
  public:
   TabGroupEditorBubbleViewDialogBrowserTestWithSavedGroupV2() {
-    scoped_feature_list_.InitWithFeatures(
-        {features::kTabGroupsSave, tab_groups::kTabGroupsSaveUIUpdate}, {});
+    scoped_feature_list_.InitWithFeatures({tab_groups::kTabGroupsSaveUIUpdate},
+                                          {});
   }
 
  private:
diff --git a/chrome/browser/ui/views/tabs/tab_group_header.cc b/chrome/browser/ui/views/tabs/tab_group_header.cc
index ce2b7fe..0e899af5 100644
--- a/chrome/browser/ui/views/tabs/tab_group_header.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_header.cc
@@ -582,11 +582,8 @@
 }
 
 bool TabGroupHeader::ShouldShowSyncIcon() const {
-  const bool is_group_saved =
-      saved_tab_group_service_ && saved_tab_group_service_->model() &&
-      saved_tab_group_service_->model()->Contains(group().value());
-  return base::FeatureList::IsEnabled(features::kTabGroupsSave) &&
-         is_group_saved;
+  return saved_tab_group_service_ && saved_tab_group_service_->model() &&
+         saved_tab_group_service_->model()->Contains(group().value());
 }
 
 void TabGroupHeader::RemoveObserverFromWidget(views::Widget* widget) {
diff --git a/chrome/browser/ui/views/tabs/tab_group_style.cc b/chrome/browser/ui/views/tabs/tab_group_style.cc
index 13cacb9..495cc08f 100644
--- a/chrome/browser/ui/views/tabs/tab_group_style.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_style.cc
@@ -26,12 +26,10 @@
 constexpr int kHeaderChipVerticalInset = 1;
 constexpr int kTitleAdjustmentForEmptyHeader = 2;
 constexpr int kTitleAdjustmentForNonEmptyHeader = -2;
-// The default size of an empty chip in the tab group header.
-constexpr int kEmptyChipSize = 14;
 // The width of the sync icon when a tab group is saved.
 constexpr int kSyncIconWidth = 16;
-// The size of the empty chips when the #tab-groups-save flag is on.
-constexpr int kSavedEmptyChipSize = 22;
+// The size of the empty chips.
+constexpr int kEmptyChipSize = 22;
 
 constexpr int kChromeRefreshHeaderChipVerticalInset = 2;
 constexpr int kChromeRefreshEmptyChipSize = 20;
@@ -122,9 +120,7 @@
 }
 
 float TabGroupStyle::GetEmptyChipSize() const {
-  return base::FeatureList::IsEnabled(features::kTabGroupsSave)
-             ? kSavedEmptyChipSize
-             : kEmptyChipSize;
+  return kEmptyChipSize;
 }
 
 float TabGroupStyle::GetSyncIconWidth() const {
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs/chrome_labs_view_controller.cc b/chrome/browser/ui/views/toolbar/chrome_labs/chrome_labs_view_controller.cc
index 534a655..345f883 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs/chrome_labs_view_controller.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs/chrome_labs_view_controller.cc
@@ -55,7 +55,7 @@
   kWebUITabStripSelected = 6,
   // kTabSearchMediaTabsSelected = 7,
   kChromeRefresh2023Selected = 8,
-  kTabGroupsSaveSelected = 9,
+  // kTabGroupsSaveSelected = 9,
   kChromeWebuiRefresh2023Selected = 10,
   kCustomizeChromeSidePanelSelected = 11,
   kMaxValue = kCustomizeChromeSidePanelSelected,
@@ -80,9 +80,6 @@
   };
 
   const auto get_enum = [](const std::string& internal_name) {
-    if (internal_name == flag_descriptions::kTabGroupsSaveId) {
-      return ChromeLabsSelectedLab::kTabGroupsSaveSelected;
-    }
     if (internal_name == flag_descriptions::kChromeRefresh2023Id) {
       return ChromeLabsSelectedLab::kChromeRefresh2023Selected;
     }
diff --git a/chrome/browser/ui/views/toolbar/toolbar_controller_interactive_uitest.cc b/chrome/browser/ui/views/toolbar/toolbar_controller_interactive_uitest.cc
index 0417e77a..4f8401e 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_controller_interactive_uitest.cc
@@ -613,8 +613,8 @@
 // overflow button should show. Verify: The pinned extension button should still
 // be visible because there's enough space for it. Extensions container should
 // not have animation because its visibility didn't change.
-// TODO(crbug.com/41495158): Flaky on Windows.
-#if BUILDFLAG(IS_WIN)
+// TODO(crbug.com/41495158): Flaky on Windows and Mac.
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
 #define MAYBE_ExtensionHasNoAnimationLoop DISABLED_ExtensionHasNoAnimationLoop
 #else
 #define MAYBE_ExtensionHasNoAnimationLoop ExtensionHasNoAnimationLoop
diff --git a/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog.cc b/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog.cc
index d2c58f6..7d1489ec 100644
--- a/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog.cc
@@ -5,16 +5,16 @@
 #include <memory>
 #include <string>
 
-#include "base/memory/scoped_refptr.h"
+#include "base/functional/callback_helpers.h"
 #include "base/metrics/user_metrics.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/feature_engagement/tracker_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/url_identity.h"
-#include "chrome/browser/ui/views/chrome_layout_provider.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/controls/site_icon_text_and_origin_view.h"
 #include "chrome/browser/ui/views/web_apps/web_app_icon_name_and_origin_view.h"
 #include "chrome/browser/ui/views/web_apps/web_app_info_image_source.h"
 #include "chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.h"
@@ -28,12 +28,9 @@
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/feature_engagement/public/tracker.h"
 #include "components/strings/grit/components_strings.h"
-#include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "components/webapps/browser/installable/ml_install_operation_tracker.h"
 #include "content/public/browser/web_contents.h"
-#include "ui/base/interaction/element_identifier.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/models/dialog_model.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/gfx/image/image_skia.h"
@@ -41,10 +38,6 @@
 #include "ui/views/bubble/bubble_dialog_model_host.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/controls/label.h"
-#include "ui/views/controls/textfield/textfield.h"
-#include "ui/views/controls/textfield/textfield_controller.h"
-#include "ui/views/layout/layout_provider.h"
-#include "ui/views/layout/table_layout.h"
 #include "ui/views/view.h"
 #include "ui/views/view_class_properties.h"
 #include "ui/views/view_utils.h"
@@ -62,133 +55,6 @@
 
 bool g_auto_accept_diy_dialog_for_testing = false;
 
-std::u16string GetTrimmedAppTitle(std::u16string app_title) {
-  base::TrimWhitespace(app_title, base::TRIM_ALL, &app_title);
-  return app_title;
-}
-
-DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kOkButtonId);
-
-class DiyAppDialogIconNameAndOriginView : public views::View,
-                                          views::TextfieldController {
-  METADATA_HEADER(DiyAppDialogIconNameAndOriginView, views::View)
- public:
-  static std::unique_ptr<DiyAppDialogIconNameAndOriginView> Create(
-      const gfx::ImageSkia& icon_image,
-      std::u16string app_title,
-      const GURL& start_url,
-      ui::DialogModel* dialog_model,
-      content::WebContents* web_contents,
-      web_app::DiyAppTitleFieldTextTracker text_tracker) {
-    return base::WrapUnique(new DiyAppDialogIconNameAndOriginView(
-        icon_image, app_title, start_url, dialog_model, web_contents,
-        text_tracker));
-  }
-
-  ~DiyAppDialogIconNameAndOriginView() override = default;
-
- private:
-  DiyAppDialogIconNameAndOriginView(
-      const gfx::ImageSkia& icon_image,
-      std::u16string app_title,
-      const GURL& start_url,
-      ui::DialogModel* dialog_model,
-      content::WebContents* web_contents,
-      web_app::DiyAppTitleFieldTextTracker text_tracker)
-      : dialog_model_(dialog_model),
-        web_contents_(web_contents),
-        text_tracker_(text_tracker) {
-    const ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
-
-    const int textfield_width = 320;
-    auto* layout = SetLayoutManager(std::make_unique<views::TableLayout>());
-    layout
-        ->AddColumn(views::LayoutAlignment::kStretch,
-                    views::LayoutAlignment::kCenter,
-                    views::TableLayout::kFixedSize,
-                    views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
-        .AddPaddingColumn(views::TableLayout::kFixedSize,
-                          layout_provider->GetDistanceMetric(
-                              views::DISTANCE_RELATED_CONTROL_HORIZONTAL))
-        .AddColumn(views::LayoutAlignment::kStretch,
-                   views::LayoutAlignment::kCenter,
-                   views::TableLayout::kFixedSize,
-                   views::TableLayout::ColumnSize::kFixed, textfield_width, 0)
-        .AddRows(1, views::TableLayout::kFixedSize)
-        .AddPaddingRow(views::TableLayout::kFixedSize,
-                       layout_provider->GetDistanceMetric(
-                           views::DISTANCE_RELATED_CONTROL_VERTICAL))
-        .AddRows(1, views::TableLayout::kFixedSize);
-
-    auto icon_view = std::make_unique<views::ImageView>();
-    icon_view->SetImage(ui::ImageModel::FromImageSkia(icon_image));
-    AddChildView(icon_view.release());
-
-    text_tracker_->data = web_app::NormalizeSuggestedAppTitle(app_title);
-
-    AddChildView(views::Builder<views::Textfield>()
-                     .CopyAddressTo(&title_field_)
-                     .SetText(text_tracker_->data)
-                     .SetAccessibleName(l10n_util::GetStringUTF16(
-                         IDS_DIY_APP_AX_BUBBLE_NAME_LABEL))
-                     .SetController(this)
-                     .Build());
-
-    // Skip the first column in the 2nd row, that is the area below the icon and
-    // should stay empty.
-    AddChildView(views::Builder<views::View>().Build());
-
-    AddChildView(
-        web_app::CreateOriginLabelFromStartUrl(start_url,
-                                               /*is_primary_text=*/false)
-            .release());
-
-    title_field_->SetID(web_app::kTextFieldId);
-    title_field_->SelectAll(true);
-  }
-
-  void ContentsChanged(views::Textfield* sender,
-                       const std::u16string& new_contents) override {
-    CHECK_EQ(sender, title_field_);
-    std::u16string trimmed_title = GetTrimmedAppTitle(new_contents);
-    auto* ok_button = dialog_model_->GetButtonByUniqueId(kOkButtonId);
-    bool ok_button_currently_enabled = ok_button->is_enabled();
-    bool current_string_empty = trimmed_title.empty();
-
-    text_tracker_->data = std::move(trimmed_title);
-
-    if (ok_button_currently_enabled && current_string_empty) {
-      dialog_model_->SetButtonEnabled(ok_button, /*enabled=*/false);
-    } else if (!ok_button_currently_enabled && !current_string_empty) {
-      dialog_model_->SetButtonEnabled(ok_button, /*enabled=*/true);
-    }
-
-    // TODO(crbug.com/328588659): This shouldn't be needed but we need to undo
-    // any position changes that are currently incorrectly caused by a
-    // SizeToContents() call, leading to the dialog being anchored off screen
-    // from the Chrome window.
-    Browser* browser = chrome::FindBrowserWithTab(web_contents_);
-    CHECK(browser);
-    web_app::WebAppInstallDialogCoordinator* coordinator =
-        web_app::WebAppInstallDialogCoordinator::FromBrowser(browser);
-    CHECK(coordinator);
-
-    constrained_window::UpdateWebContentsModalDialogPosition(
-        coordinator->GetBubbleView()->GetWidget(),
-        web_modal::WebContentsModalDialogManager::FromWebContents(web_contents_)
-            ->delegate()
-            ->GetWebContentsModalDialogHost());
-  }
-
-  raw_ptr<views::Textfield> title_field_ = nullptr;
-  raw_ptr<ui::DialogModel> dialog_model_ = nullptr;
-  raw_ptr<content::WebContents> web_contents_ = nullptr;
-  web_app::DiyAppTitleFieldTextTracker text_tracker_;
-};
-
-BEGIN_METADATA(DiyAppDialogIconNameAndOriginView)
-END_METADATA
-
 #if BUILDFLAG(IS_CHROMEOS)
 namespace cros_events = metrics::structured::events::v2::cr_os_events;
 #endif
@@ -248,13 +114,10 @@
                    .name;
   }
 
-  DiyAppTitleFieldTextTracker data =
-      base::MakeRefCounted<base::RefCountedData<std::u16string>>();
-
   auto delegate = std::make_unique<web_app::WebAppInstallDialogDelegate>(
       web_contents, std::move(web_app_info), std::move(install_tracker),
       std::move(callback), std::move(iph_state), prefs, tracker,
-      InstallDialogType::kDiy, data);
+      InstallDialogType::kDiy);
   auto delegate_weak_ptr = delegate->AsWeakPtr();
 
   auto dialog_model =
@@ -263,11 +126,12 @@
           .SetTitle(l10n_util::GetStringUTF16(IDS_DIY_APP_INSTALL_DIALOG_TITLE))
           .SetSubtitle(
               l10n_util::GetStringUTF16(IDS_DIY_APP_INSTALL_DIALOG_SUBTITLE))
-          .AddOkButton(base::BindOnce(&WebAppInstallDialogDelegate::OnAccept,
-                                      delegate_weak_ptr),
-                       ui::DialogModel::Button::Params()
-                           .SetLabel(l10n_util::GetStringUTF16(IDS_INSTALL))
-                           .SetId(kOkButtonId))
+          .AddOkButton(
+              base::BindOnce(&WebAppInstallDialogDelegate::OnAccept,
+                             delegate_weak_ptr),
+              ui::DialogModel::Button::Params()
+                  .SetLabel(l10n_util::GetStringUTF16(IDS_INSTALL))
+                  .SetId(WebAppInstallDialogDelegate::kDiyAppsDialogOkButtonId))
           .AddCancelButton(base::BindOnce(
               &WebAppInstallDialogDelegate::OnCancel, delegate_weak_ptr))
           .SetCloseActionCallback(base::BindOnce(
@@ -277,13 +141,16 @@
           .OverrideDefaultButton(ui::DialogButton::DIALOG_BUTTON_NONE)
           .Build();
 
-  auto* dialog_model_ptr = dialog_model.get();
-  dialog_model->AddCustomField(
-      std::make_unique<views::BubbleDialogModelHost::CustomView>(
-          DiyAppDialogIconNameAndOriginView::Create(icon_image, app_name,
-                                                    start_url, dialog_model_ptr,
-                                                    web_contents, data),
-          views::BubbleDialogModelHost::FieldType::kControl));
+  dialog_model->AddCustomField(std::make_unique<
+                               views::BubbleDialogModelHost::CustomView>(
+      std::make_unique<SiteIconTextAndOriginView>(
+          icon_image, app_name,
+          l10n_util::GetStringUTF16(IDS_DIY_APP_AX_BUBBLE_NAME_LABEL),
+          start_url, web_contents,
+          base::BindRepeating(
+              &WebAppInstallDialogDelegate::OnTextFieldChangedMaybeUpdateButton,
+              delegate_weak_ptr)),
+      views::BubbleDialogModelHost::FieldType::kControl));
 
   auto dialog = views::BubbleDialogModelHost::CreateModal(
       std::move(dialog_model), ui::MODAL_TYPE_CHILD);
diff --git a/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog_browsertest.cc
index 83fbbcc9..b0f18ae 100644
--- a/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog_browsertest.cc
@@ -39,7 +39,9 @@
     auto install_info = std::make_unique<WebAppInstallInfo>(
         GenerateManifestIdFromStartUrlOnly(GURL("https://example.com")));
 
-    if (name != "empty_name") {
+    if (name == "name_with_spaces") {
+      install_info->title = u"       trimmed app    ";
+    } else if (name != "empty_name") {
       install_info->title = u"test";
     }
 
@@ -182,6 +184,32 @@
             u"example.com");
 }
 
+IN_PROC_BROWSER_TEST_F(WebAppDiyInstallDialogBrowserTest,
+                       InvokeUi_name_with_spaces) {
+  base::UserActionTester action_tester;
+  EXPECT_TRUE(
+      ui_test_utils::NavigateToURL(browser(), GURL("https://example.com")));
+
+  base::test::TestFuture<bool, std::unique_ptr<WebAppInstallInfo>>
+      dialog_future;
+  OverrideDialogCallback(dialog_future.GetCallback());
+
+  views::NamedWidgetShownWaiter widget_waiter(
+      views::test::AnyWidgetTestPasskey{}, "WebAppDiyInstallDialog");
+  ShowUi("name_with_spaces");
+  views::Widget* widget = widget_waiter.WaitIfNeededAndGet();
+
+  views::test::WidgetDestroyedWaiter destroy_waiter(widget);
+  views::test::AcceptDialog(widget);
+  destroy_waiter.Wait();
+  EXPECT_TRUE(dialog_future.Wait());
+
+  auto dialog_results = dialog_future.Take();
+  EXPECT_TRUE(std::get<bool>(dialog_results));
+  EXPECT_EQ(std::get<std::unique_ptr<WebAppInstallInfo>>(dialog_results)->title,
+            u"trimmed app");
+}
+
 }  // namespace
 
 }  // namespace web_app
diff --git a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc
index e476a9de..951be82 100644
--- a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc
@@ -61,6 +61,9 @@
   return normalized;
 }
 
+DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(WebAppInstallDialogDelegate,
+                                      kDiyAppsDialogOkButtonId);
+
 WebAppInstallDialogDelegate::WebAppInstallDialogDelegate(
     content::WebContents* web_contents,
     std::unique_ptr<web_app::WebAppInstallInfo> web_app_info,
@@ -69,8 +72,7 @@
     PwaInProductHelpState iph_state,
     PrefService* prefs,
     feature_engagement::Tracker* tracker,
-    InstallDialogType dialog_type,
-    DiyAppTitleFieldTextTracker title_field_data)
+    InstallDialogType dialog_type)
     : content::WebContentsObserver(web_contents),
       web_contents_(web_contents),
       install_info_(std::move(web_app_info)),
@@ -79,8 +81,7 @@
       iph_state_(std::move(iph_state)),
       prefs_(prefs),
       tracker_(tracker),
-      dialog_type_(dialog_type),
-      title_field_data_(title_field_data) {
+      dialog_type_(dialog_type) {
   CHECK(install_info_);
   CHECK(install_info_->manifest_id.is_valid());
   CHECK(install_tracker_);
@@ -131,7 +132,8 @@
   // DIY apps get their name from the DIY install dialog and are always set to
   // open in a new window.
   if (dialog_type_ == InstallDialogType::kDiy) {
-    install_info_->title = title_field_data_->data;
+    CHECK(!text_field_contents_.empty());
+    install_info_->title = text_field_contents_;
     install_info_->user_display_mode =
         web_app::mojom::UserDisplayMode::kStandalone;
   }
@@ -154,6 +156,16 @@
   MeasureIphOnDialogClose();
 }
 
+void WebAppInstallDialogDelegate::OnTextFieldChangedMaybeUpdateButton(
+    const std::u16string& text_field_contents) {
+  text_field_contents_ = text_field_contents;
+  ui::DialogModel::Button* ok_button =
+      dialog_model()->GetButtonByUniqueId(kDiyAppsDialogOkButtonId);
+  CHECK(ok_button);
+  dialog_model()->SetButtonEnabled(ok_button,
+                                   /*enabled=*/!text_field_contents.empty());
+}
+
 void WebAppInstallDialogDelegate::OnVisibilityChanged(
     content::Visibility visibility) {
   if (visibility == content::Visibility::HIDDEN) {
diff --git a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h
index f709b315..d64e9a8 100644
--- a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h
+++ b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h
@@ -9,8 +9,6 @@
 #include <string>
 
 #include "base/memory/raw_ptr.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ui/web_applications/web_app_dialogs.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
@@ -57,15 +55,12 @@
 // result in a weird filename), it only restricts what we suggest as titles.
 std::u16string NormalizeSuggestedAppTitle(const std::u16string& title);
 
-inline constexpr int kTextFieldId = 1;
-
-using DiyAppTitleFieldTextTracker =
-    scoped_refptr<base::RefCountedData<std::u16string>>;
-
 class WebAppInstallDialogDelegate
     : public ui::DialogModelDelegate,
       public content::WebContentsObserver {
  public:
+  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kDiyAppsDialogOkButtonId);
+
   WebAppInstallDialogDelegate(
       content::WebContents* web_contents,
       std::unique_ptr<WebAppInstallInfo> install_info,
@@ -74,9 +69,7 @@
       PwaInProductHelpState iph_state,
       PrefService* prefs,
       feature_engagement::Tracker* tracker,
-      InstallDialogType dialog_type,
-      DiyAppTitleFieldTextTracker title_field_data =
-          base::MakeRefCounted<base::RefCountedData<std::u16string>>());
+      InstallDialogType dialog_type);
 
   ~WebAppInstallDialogDelegate() override;
 
@@ -84,6 +77,12 @@
   void OnCancel();
   void OnClose();
 
+  // Takes care of enabling or disabling the dialog model's OK button for DIY
+  // apps based on changes in the text field, and also keeps track of the text
+  // field's contents.
+  void OnTextFieldChangedMaybeUpdateButton(
+      const std::u16string& text_field_contents);
+
   base::WeakPtr<WebAppInstallDialogDelegate> AsWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
   }
@@ -108,7 +107,7 @@
   raw_ptr<PrefService> prefs_;
   raw_ptr<feature_engagement::Tracker> tracker_;
   InstallDialogType dialog_type_;
-  DiyAppTitleFieldTextTracker title_field_data_;
+  std::u16string text_field_contents_;
 
   base::WeakPtrFactory<WebAppInstallDialogDelegate> weak_ptr_factory_{
       this};
diff --git a/chrome/browser/ui/views/webauthn/authenticator_request_bubble.cc b/chrome/browser/ui/views/webauthn/authenticator_request_bubble.cc
index 5848bba..53f21701 100644
--- a/chrome/browser/ui/views/webauthn/authenticator_request_bubble.cc
+++ b/chrome/browser/ui/views/webauthn/authenticator_request_bubble.cc
@@ -30,12 +30,6 @@
 #include "ui/views/style/typography.h"
 #include "ui/views/view_class_properties.h"
 
-#if BUILDFLAG(IS_MAC)
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/views/webauthn/mac_authentication_view.h"
-#include "crypto/scoped_lacontext.h"
-#endif  // BUILDFLAG(IS_MAC)
-
 namespace {
 
 struct BubbleContents {
@@ -51,13 +45,6 @@
       &AuthenticatorRequestDialogModel::StartOver;
 };
 
-constexpr BubbleContents kGPMTouchID = {
-    .title = u"Touch ID to proceed (UNTRANSLATED)",
-    .body = nullptr,
-    .show_footer = false,
-    .on_ok = &AuthenticatorRequestDialogModel::OnGPMCreatePasskey,
-};
-
 // TODO(rgod): Add username row and correct footer when mocks are ready.
 constexpr BubbleContents kGPMPasskeySavedContents = {
     .buttons = ui::DIALOG_BUTTON_NONE,
@@ -110,8 +97,6 @@
   static const BubbleContents* GetContents(
       AuthenticatorRequestDialogModel::Step step) {
     switch (step) {
-      case AuthenticatorRequestDialogModel::Step::kGPMTouchID:
-        return &kGPMTouchID;
       case AuthenticatorRequestDialogModel::Step::kGPMPasskeySaved:
         return &kGPMPasskeySavedContents;
       default:
@@ -235,18 +220,6 @@
               .SetTextContext(views::style::CONTEXT_DIALOG_BODY_TEXT)
               .Build());
     }
-
-#if BUILDFLAG(IS_MAC)
-    if (step_ == AuthenticatorRequestDialogModel::Step::kGPMTouchID) {
-      if (__builtin_available(macos 12, *)) {
-        primary_view_->AddChildView(
-            std::make_unique<MacAuthenticationView>(base::BindOnce(
-                &AuthenticatorRequestBubbleDelegate::OnTouchIDContextReady,
-                weak_ptr_factory_.GetWeakPtr())));
-      }
-    }
-#endif  // BUILDFLAG(IS_MAC)
-
     AddChildView(std::move(primary_view));
   }
 
@@ -255,22 +228,10 @@
     UpdateHeader();
     UpdateFootnote();
   }
-
-#if BUILDFLAG(IS_MAC)
-  void OnTouchIDContextReady(std::optional<crypto::ScopedLAContext> lacontext) {
-    model_->lacontext = std::move(lacontext);
-    model_->OnTouchIDComplete(model_->lacontext.has_value());
-  }
-#endif  // BUILDFLAG(IS_MAC)
-
   raw_ptr<AuthenticatorRequestDialogModel> model_;
   AuthenticatorRequestDialogModel::Step step_;
   raw_ptr<const BubbleContents> bubble_contents_;
   raw_ptr<views::View> primary_view_;
-#if BUILDFLAG(IS_MAC)
-  base::WeakPtrFactory<AuthenticatorRequestBubbleDelegate> weak_ptr_factory_{
-      this};
-#endif  // BUILDFLAG(IS_MAC)
 };
 
 }  // namespace
diff --git a/chrome/browser/ui/views/webauthn/authenticator_touch_id_view.cc b/chrome/browser/ui/views/webauthn/authenticator_touch_id_view.cc
new file mode 100644
index 0000000..9196502e3
--- /dev/null
+++ b/chrome/browser/ui/views/webauthn/authenticator_touch_id_view.cc
@@ -0,0 +1,72 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/webauthn/authenticator_touch_id_view.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/functional/bind.h"
+#include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
+#include "chrome/browser/ui/views/webauthn/mac_authentication_view.h"
+#include "chrome/browser/ui/views/webauthn/passkey_detail_view.h"
+#include "chrome/browser/ui/webauthn/sheet_models.h"
+#include "components/vector_icons/vector_icons.h"
+#include "crypto/scoped_lacontext.h"
+#include "device/fido/fido_constants.h"
+#include "device/fido/mac/util.h"
+#include "ui/color/color_id.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/box_layout_view.h"
+#include "ui/views/view.h"
+
+namespace {
+constexpr int kVerticalPadding = 20;
+
+// The size if the lock icon that replaces the Touch ID prompt when locked
+// (square).
+constexpr int kLockIconSize = 64;
+}  // namespace
+
+AuthenticatorTouchIdView::AuthenticatorTouchIdView(
+    std::unique_ptr<AuthenticatorTouchIdSheetModel> sheet_model)
+    : AuthenticatorRequestSheetView(std::move(sheet_model)) {}
+
+AuthenticatorTouchIdView::~AuthenticatorTouchIdView() = default;
+
+std::pair<std::unique_ptr<views::View>, AuthenticatorTouchIdView::AutoFocus>
+AuthenticatorTouchIdView::BuildStepSpecificContent() {
+  auto container = std::make_unique<views::BoxLayoutView>();
+  auto* dialog_model =
+      static_cast<AuthenticatorTouchIdSheetModel*>(model())->dialog_model();
+  bool is_get_assertion =
+      dialog_model->request_type == device::FidoRequestType::kGetAssertion;
+  container->SetOrientation(views::BoxLayout::Orientation::kVertical);
+  container->SetBetweenChildSpacing(kVerticalPadding);
+  container->AddChildView(std::make_unique<PasskeyDetailView>(
+      is_get_assertion ? dialog_model->preselected_cred->user
+                       : dialog_model->user_entity));
+  if (device::fido::mac::DeviceHasBiometricsAvailable()) {
+    container->AddChildView(std::make_unique<MacAuthenticationView>(
+        base::BindOnce(&AuthenticatorTouchIdView::OnTouchIDComplete,
+                       base::Unretained(this))));
+    container->AddChildView(
+        std::make_unique<views::Label>(u"Touch ID to continue (UT)"));
+  } else {
+    container->AddChildView(
+        std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
+            vector_icons::kLockIcon, ui::kColorMenuIcon, kLockIconSize)));
+    container->AddChildView(std::make_unique<views::Label>(
+        u"Touch ID locked. Use your password to unlock. (UT)"));
+  }
+  return {std::move(container), AutoFocus::kNo};
+}
+
+void AuthenticatorTouchIdView::OnTouchIDComplete(
+    std::optional<crypto::ScopedLAContext> lacontext) {
+  static_cast<AuthenticatorTouchIdSheetModel*>(model())->OnTouchIDSensorTapped(
+      std::move(lacontext));
+}
diff --git a/chrome/browser/ui/views/webauthn/authenticator_touch_id_view.h b/chrome/browser/ui/views/webauthn/authenticator_touch_id_view.h
new file mode 100644
index 0000000..fa31b9cf
--- /dev/null
+++ b/chrome/browser/ui/views/webauthn/authenticator_touch_id_view.h
@@ -0,0 +1,38 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_TOUCH_ID_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_TOUCH_ID_VIEW_H_
+
+#include <os/availability.h>
+
+#include <optional>
+
+#include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
+#include "chrome/browser/ui/webauthn/sheet_models.h"
+#include "crypto/scoped_lacontext.h"
+
+// Displays a sheet prompting the user to tap their Touch ID sensor to complete
+// a passkey flow.
+class API_AVAILABLE(macos(12.0)) AuthenticatorTouchIdView
+    : public AuthenticatorRequestSheetView {
+ public:
+  explicit AuthenticatorTouchIdView(
+      std::unique_ptr<AuthenticatorTouchIdSheetModel> sheet_model);
+
+  AuthenticatorTouchIdView(const AuthenticatorTouchIdView&) = delete;
+  AuthenticatorTouchIdView& operator=(const AuthenticatorTouchIdView&) = delete;
+
+  ~AuthenticatorTouchIdView() override;
+
+ private:
+  // Called after the user taps their Touch ID sensor.
+  void OnTouchIDComplete(std::optional<crypto::ScopedLAContext> lacontext);
+
+  // AuthenticatorRequestSheetView:
+  std::pair<std::unique_ptr<views::View>, AutoFocus> BuildStepSpecificContent()
+      override;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_TOUCH_ID_VIEW_H_
diff --git a/chrome/browser/ui/views/webauthn/mac_authentication_view.h b/chrome/browser/ui/views/webauthn/mac_authentication_view.h
index 557387b..1e8948a4 100644
--- a/chrome/browser/ui/views/webauthn/mac_authentication_view.h
+++ b/chrome/browser/ui/views/webauthn/mac_authentication_view.h
@@ -10,6 +10,7 @@
 #include "base/functional/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
 #include "crypto/scoped_lacontext.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/view.h"
@@ -42,10 +43,12 @@
   struct ObjCStorage;
 
   void OnAuthenticationComplete(bool success);
+  void OnTouchIDAnimationComplete(bool success);
 
   Callback callback_;
   std::unique_ptr<ObjCStorage> storage_;
   bool evaluation_requested_ = false;
+  base::OneShotTimer touch_id_animation_timer_;
 
   base::WeakPtrFactory<MacAuthenticationView> weak_factory_{this};
 };
diff --git a/chrome/browser/ui/views/webauthn/mac_authentication_view.mm b/chrome/browser/ui/views/webauthn/mac_authentication_view.mm
index 0e3615662..9f2935a 100644
--- a/chrome/browser/ui/views/webauthn/mac_authentication_view.mm
+++ b/chrome/browser/ui/views/webauthn/mac_authentication_view.mm
@@ -8,16 +8,25 @@
 #import <LocalAuthenticationEmbeddedUI/LocalAuthenticationEmbeddedUI.h>
 
 #include "base/logging.h"
+#include "base/timer/timer.h"
 #include "components/device_event_log/device_event_log.h"
 #include "content/public/browser/browser_thread.h"
 #include "crypto/scoped_lacontext.h"
+#include "device/fido/mac/util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/canvas.h"
 #include "ui/views/widget/widget.h"
 
-// kWidth is the width (and height, since it's square) of the NSView. This
-// particular width matches Safari, but isn't any of the predefined sizes.
-constexpr int kWidth = 40;
+// kWidth is the width (and height, since it's square) of the NSView.
+constexpr int kWidth = 64;
+
+// The seconds it takes for the Touch ID animation to finish when the challenge
+// fails.
+constexpr float kErrorAnimationLength = 1;
+
+// The seconds it takes for the Touch ID animation to finish when the challenge
+// succeeds.
+constexpr float kSuccessAnimationLength = 2.5;
 
 struct API_AVAILABLE(macos(12.0)) MacAuthenticationView::ObjCStorage {
   LAContext* __strong context;
@@ -96,6 +105,12 @@
 void MacAuthenticationView::OnPaint(gfx::Canvas* canvas) {
   views::View::OnPaint(canvas);
   if (GetVisible() && !evaluation_requested_) {
+    InvalidateLayout();
+    storage_->auth_view.hidden = false;
+    evaluation_requested_ = true;
+    if (!device::fido::mac::DeviceHasBiometricsAvailable()) {
+      return;
+    }
     __block auto internal_callback =
         base::BindOnce(&MacAuthenticationView::OnAuthenticationComplete,
                        weak_factory_.GetWeakPtr());
@@ -113,7 +128,6 @@
                                    base::BindOnce(std::move(internal_callback),
                                                   success));
                   }];
-    evaluation_requested_ = true;
   }
 }
 
@@ -125,6 +139,17 @@
 
 void MacAuthenticationView::OnAuthenticationComplete(bool success) {
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+  // It takes a while for the Touch ID animation to finish after success is
+  // reported. Avoid jank by waiting for the animation to finish before
+  // notifying the client.
+  touch_id_animation_timer_.Start(
+      FROM_HERE,
+      base::Seconds(success ? kSuccessAnimationLength : kErrorAnimationLength),
+      base::BindOnce(&MacAuthenticationView::OnTouchIDAnimationComplete,
+                     base::Unretained(this), success));
+}
+
+void MacAuthenticationView::OnTouchIDAnimationComplete(bool success) {
   std::optional<crypto::ScopedLAContext> lacontext;
   if (success) {
     lacontext.emplace(storage_->context);
diff --git a/chrome/browser/ui/views/webauthn/sheet_view_factory.cc b/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
index db50d44..919890e 100644
--- a/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
+++ b/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/check.h"
+#include "base/notreached.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/autofill/payments/webauthn_dialog_model.h"
 #include "chrome/browser/ui/views/webauthn/authenticator_bio_enrollment_sheet_view.h"
@@ -31,6 +32,10 @@
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/box_layout_view.h"
 
+#if BUILDFLAG(IS_MAC)
+#include "chrome/browser/ui/views/webauthn/authenticator_touch_id_view.h"
+#endif  // BUILDFLAG(IS_MAC)
+
 namespace {
 
 // Number of digits for the GPM Pin.
@@ -377,12 +382,24 @@
       sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
           std::make_unique<AuthenticatorGpmOnboardingSheetModel>(dialog_model));
       break;
+    case Step::kGPMTouchID:
+#if BUILDFLAG(IS_MAC)
+      if (__builtin_available(macOS 12.0, *)) {
+        sheet_view = std::make_unique<AuthenticatorTouchIdView>(
+            std::make_unique<AuthenticatorTouchIdSheetModel>(dialog_model));
+      } else {
+        NOTREACHED_NORETURN()
+            << "MacOS version does not support LAAuthenticationView";
+      }
+#else
+      sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
+          std::make_unique<PlaceholderSheetModel>(dialog_model));
+#endif
+      break;
     case Step::kNotStarted:
     case Step::kConditionalMediation:
     case Step::kClosed:
     case Step::kRecoverSecurityDomain:
-    case Step::kWaitingForEnclave:
-    case Step::kGPMTouchID:
     case Step::kGPMPasskeySaved:
     case Step::kGPMReauthAccount:
       sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
index 423b086c..3cfe8a2 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
@@ -685,6 +685,11 @@
       GetDialogWidget()->Hide();
     }
   }
+
+  // If this happens after ShowVerifyingSheet, notify_delegate_of_dismiss_ may
+  // be false. However, if the user closes the popup, we do want to call
+  // OnDismiss to ensure the request is cancelled, so set it to true here.
+  notify_delegate_of_dismiss_ = true;
   return popup_window_->ShowPopupWindow(url);
 }
 
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
index a8b021f5..4a5b6fe 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
@@ -161,6 +161,8 @@
                            IdpSigninStatusPopupClosedBeforeAccountsPopulated);
   FRIEND_TEST_ALL_PREFIXES(FedCmAccountSelectionViewDesktopTest,
                            IdpSigninStatusPopupClosedAfterAccountsPopulated);
+  FRIEND_TEST_ALL_PREFIXES(FedCmAccountSelectionViewDesktopTest,
+                           ClosePopupAfterVerifyingSheetShouldNotify);
 
   enum class State {
     // User is shown message that they are not currently signed-in to IdP.
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
index ac361f0..f9179f25 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
@@ -233,7 +233,7 @@
   gfx::NativeView GetNativeView() override { return gfx::NativeView(); }
 
   content::WebContents* GetWebContents() override { return web_contents_; }
-  const DismissReason& GetDismissReason() { return dismiss_reason_; }
+  std::optional<DismissReason> GetDismissReason() { return dismiss_reason_; }
 
   void SetOnDismissClosure(base::OnceClosure on_dismiss) {
     on_dismiss_ = std::move(on_dismiss);
@@ -241,7 +241,7 @@
 
  private:
   raw_ptr<content::WebContents> web_contents_;
-  DismissReason dismiss_reason_;
+  std::optional<DismissReason> dismiss_reason_;
   base::OnceClosure on_dismiss_;
 };
 
@@ -1738,6 +1738,34 @@
   controller->CloseModalDialog();
 }
 
+// Tests that opening a popup after a verifying sheet, then closing the popup,
+// notifies the observer.
+TEST_F(FedCmAccountSelectionViewDesktopTest,
+       ClosePopupAfterVerifyingSheetShouldNotify) {
+  const char kAccountId[] = "account_id";
+  IdentityProviderDisplayData idp_data =
+      CreateIdentityProviderDisplayData({{kAccountId, LoginState::kSignUp}});
+  const std::vector<Account>& accounts = idp_data.accounts;
+  std::unique_ptr<TestFedCmAccountSelectionView> controller =
+      CreateAndShow(accounts, SignInMode::kExplicit);
+  AccountSelectionViewBase::Observer* observer =
+      static_cast<AccountSelectionViewBase::Observer*>(controller.get());
+
+  EXPECT_FALSE(account_selection_view_->show_back_button_);
+  EXPECT_EQ(TestAccountSelectionView::SheetType::kConfirmAccount,
+            account_selection_view_->sheet_type_);
+  EXPECT_THAT(account_selection_view_->account_ids_,
+              testing::ElementsAre(kAccountId));
+
+  observer->OnAccountSelected(accounts[0], idp_data, CreateMouseEvent());
+  EXPECT_EQ(TestAccountSelectionView::SheetType::kVerifying,
+            account_selection_view_->sheet_type_);
+
+  CreateAndShowPopupWindow(*controller);
+  controller->popup_window_->ClosePopupWindow();
+  EXPECT_EQ(delegate_->GetDismissReason(), DismissReason::kOther);
+}
+
 // Tests that if the dialog skips requesting permission, the verifying sheet is
 // shown.
 TEST_F(FedCmAccountSelectionViewDesktopTest,
diff --git a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc
index 761d75f..6301b26 100644
--- a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc
+++ b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc
@@ -92,6 +92,9 @@
     observer_->OnPopupWindowDestroyed();
   }
 
+  // The popup window is going away, make sure we don't keep a danging pointer.
+  popup_window_ = nullptr;
+
   UMA_HISTOGRAM_ENUMERATION(
       "Blink.FedCm.IdpSigninStatus.ClosePopupWindowReason",
       FedCmModalDialogView::ClosePopupWindowReason::kPopupWindowDestroyed);
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_utils.cc b/chrome/browser/ui/web_applications/web_app_dialog_utils.cc
index c0d6e37..3ee6a92 100644
--- a/chrome/browser/ui/web_applications/web_app_dialog_utils.cc
+++ b/chrome/browser/ui/web_applications/web_app_dialog_utils.cc
@@ -82,27 +82,27 @@
     WebAppInstallationAcceptanceCallback web_app_acceptance_callback) {
   web_app_info->user_display_mode = mojom::UserDisplayMode::kStandalone;
 
-  ash::app_install::mojom::DialogArgsPtr args =
-      ash::app_install::mojom::DialogArgs::New();
-  args->url = web_app_info->start_url.GetWithEmptyPath();
-  args->name = base::UTF16ToUTF8(web_app_info->title);
-  args->description = base::UTF16ToUTF8(web_app_info->description);
   apps::IconInfo icon = GetIcon(web_app_info->manifest_icons);
-  args->icon_url = icon.url;
+
+  std::vector<ash::app_install::mojom::ScreenshotPtr> dialog_screenshots;
   for (const auto& screenshot : screenshots) {
     auto dialog_screenshot = ash::app_install::mojom::Screenshot::New();
     dialog_screenshot->url = GURL(webui::GetBitmapDataUrl(screenshot.image));
     dialog_screenshot->size =
         gfx::Size(screenshot.image.width(), screenshot.image.height());
-    args->screenshots.push_back(std::move(dialog_screenshot));
+    dialog_screenshots.push_back(std::move(dialog_screenshot));
   }
 
   dialog_handle->ShowApp(
       profile, initiator_web_contents->GetTopLevelNativeWindow(),
-      std::move(args),
+      apps::PackageId(apps::PackageType::kWeb,
+                      web_app_info->manifest_id.spec()),
+      base::UTF16ToUTF8(web_app_info->title),
+      web_app_info->start_url.GetWithEmptyPath(),
+      base::UTF16ToUTF8(web_app_info->description), icon.url,
       icon.square_size_px.has_value() ? icon.square_size_px.value() : 0,
       icon.purpose == apps::IconInfo::Purpose::kMaskable,
-      web_app::GenerateAppIdFromManifestId(web_app_info->manifest_id),
+      std::move(dialog_screenshots),
       base::BindOnce(
           [](std::unique_ptr<WebAppInstallInfo> web_app_info,
              WebAppInstallationAcceptanceCallback web_app_acceptance_callback,
@@ -119,7 +119,7 @@
     const webapps::AppId& app_id,
     webapps::InstallResultCode code) {
   if (webapps::IsSuccess(code)) {
-    dialog_handle->SetInstallSucceeded(&app_id);
+    dialog_handle->SetInstallSucceeded();
   } else {
     // If we receive an error code, there's a chance the dialog was never shown,
     // so we need to clean it up to avoid a memory leak.
diff --git a/chrome/browser/ui/web_applications/web_app_url_handling_browsertest.cc b/chrome/browser/ui/web_applications/web_app_url_handling_browsertest.cc
index 0046cf5..8ed5538 100644
--- a/chrome/browser/ui/web_applications/web_app_url_handling_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_url_handling_browsertest.cc
@@ -63,7 +63,6 @@
   base::HistogramTester histogram_tester_;
 
  private:
-  OsIntegrationManager::ScopedSuppressForTesting os_hooks_supress_;
   base::test::ScopedFeatureList scoped_feature_list_{
       blink::features::kWebAppEnableUrlHandlers};
 };
diff --git a/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc b/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc
index 5004dc69..20ea916 100644
--- a/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc
+++ b/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc
@@ -29,6 +29,7 @@
 #include "device/fido/fido_constants.h"
 #include "device/fido/fido_request_handler_base.h"
 #include "device/fido/fido_transport_protocol.h"
+#include "device/fido/fido_types.h"
 #include "device/fido/pin.h"
 #include "device/fido/public_key_credential_user_entity.h"
 #include "google_apis/gaia/gaia_switches.h"
@@ -722,6 +723,11 @@
       controller_->SetCurrentStepForTesting(
           AuthenticatorRequestDialogModel::Step::kGPMCreatePasskey);
     } else if (name == "touchid") {
+      model_->user_entity = local_cred1.user;
+      transport_availability.request_type =
+          device::FidoRequestType::kMakeCredential;
+      transport_availability.make_credential_attachment =
+          device::AuthenticatorAttachment::kAny;
       controller_->SetCurrentStepForTesting(
           AuthenticatorRequestDialogModel::Step::kGPMTouchID);
     } else if (name == "gpm_onboarding") {
@@ -852,7 +858,9 @@
 
 #if BUILDFLAG(IS_MAC)
 IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest, InvokeUi_touchid) {
-  ShowAndVerifyUi();
+  if (__builtin_available(macos 12, *)) {
+    ShowAndVerifyUi();
+  }
 }
 #endif  // BUILDFLAG(IS_MAC)
 
diff --git a/chrome/browser/ui/webauthn/sheet_models.cc b/chrome/browser/ui/webauthn/sheet_models.cc
index 65df2ba8..3eede4ce 100644
--- a/chrome/browser/ui/webauthn/sheet_models.cc
+++ b/chrome/browser/ui/webauthn/sheet_models.cc
@@ -24,11 +24,14 @@
 #include "components/strings/grit/components_strings.h"
 #include "device/fido/discoverable_credential_metadata.h"
 #include "device/fido/features.h"
+#include "device/fido/fido_constants.h"
 #include "device/fido/fido_types.h"
 #include "ui/base/l10n/l10n_util.h"
 
 #if BUILDFLAG(IS_MAC)
 #include "base/mac/mac_util.h"
+#include "crypto/scoped_lacontext.h"
+#include "device/fido/mac/util.h"
 #endif
 
 namespace {
@@ -551,6 +554,72 @@
   dialog_model()->OpenBlePreferences();
 }
 
+// AuthenticatorTouchIdSheetModel
+// ------------------------------------
+
+AuthenticatorTouchIdSheetModel::AuthenticatorTouchIdSheetModel(
+    AuthenticatorRequestDialogModel* dialog_model)
+    : AuthenticatorSheetModelBase(dialog_model,
+                                  OtherMechanismButtonVisibility::kVisible) {}
+
+std::u16string AuthenticatorTouchIdSheetModel::GetStepTitle() const {
+  switch (dialog_model()->request_type) {
+    case device::FidoRequestType::kGetAssertion:
+      return u"Use your passkey to sign in to " +
+             base::UTF8ToUTF16(dialog_model()->relying_party_id) + u"? (UT)";
+    case device::FidoRequestType::kMakeCredential:
+      return u"Create a passkey for " +
+             base::UTF8ToUTF16(dialog_model()->relying_party_id) + u"? (UT)";
+  }
+}
+
+std::u16string AuthenticatorTouchIdSheetModel::GetStepDescription() const {
+  return u"You can use saved passkeys on any device. It will be saved to "
+         u"Google Password Manager for your Google account (UT)";
+}
+
+bool AuthenticatorTouchIdSheetModel::IsAcceptButtonVisible() const {
+  return !device::fido::mac::DeviceHasBiometricsAvailable();
+}
+
+bool AuthenticatorTouchIdSheetModel::IsAcceptButtonEnabled() const {
+  return true;
+}
+
+bool AuthenticatorTouchIdSheetModel::IsCancelButtonVisible() const {
+  return true;
+}
+
+std::u16string AuthenticatorTouchIdSheetModel::GetAcceptButtonLabel() const {
+  return u"Use password (UT)";
+}
+
+void AuthenticatorTouchIdSheetModel::OnAccept() {
+  if (touch_id_completed_) {
+    return;
+  }
+  touch_id_completed_ = true;
+  dialog_model()->OnTouchIDComplete(false);
+}
+
+void AuthenticatorTouchIdSheetModel::OnTouchIDSensorTapped(
+    std::optional<crypto::ScopedLAContext> lacontext) {
+  // Ignore Touch ID ceremony status after the user has completed the ceremony.
+  if (touch_id_completed_) {
+    return;
+  }
+  if (!lacontext) {
+    // Authentication failed. Update the button status and rebuild the sheet,
+    // which will restart the Touch ID request if the sensor is not softlocked
+    // or display a padlock icon if it is.
+    dialog_model()->OnSheetModelChanged();
+    return;
+  }
+  touch_id_completed_ = true;
+  dialog_model()->lacontext = std::move(lacontext);
+  dialog_model()->OnTouchIDComplete(true);
+}
+
 #endif  // IS_MAC
 
 // AuthenticatorOffTheRecordInterstitialSheetModel
@@ -1478,11 +1547,11 @@
     return;
   }
 
-  std::optional<std::u16string> phone_name =
-      dialog_model->GetPriorityPhoneName();
+  const std::optional<std::string>& phone_name =
+      dialog_model->priority_phone_name;
   if (phone_name) {
-    primary_passkeys_label_ =
-        l10n_util::GetStringFUTF16(IDS_WEBAUTHN_FROM_PHONE_LABEL, *phone_name);
+    primary_passkeys_label_ = l10n_util::GetStringFUTF16(
+        IDS_WEBAUTHN_FROM_PHONE_LABEL, base::UTF8ToUTF16(*phone_name));
   }
   for (size_t i = 0; i < dialog_model->mechanisms.size(); ++i) {
     const AuthenticatorRequestDialogModel::Mechanism& mech =
diff --git a/chrome/browser/ui/webauthn/sheet_models.h b/chrome/browser/ui/webauthn/sheet_models.h
index f847280..1e46780 100644
--- a/chrome/browser/ui/webauthn/sheet_models.h
+++ b/chrome/browser/ui/webauthn/sheet_models.h
@@ -246,6 +246,27 @@
   void OnAccept() override;
 };
 
+class AuthenticatorTouchIdSheetModel : public AuthenticatorSheetModelBase {
+ public:
+  explicit AuthenticatorTouchIdSheetModel(
+      AuthenticatorRequestDialogModel* dialog_model);
+
+  // Called after the user taps their Touch ID sensor.
+  void OnTouchIDSensorTapped(std::optional<crypto::ScopedLAContext> lacontext);
+
+ private:
+  // AuthenticatorSheetModelBase:
+  std::u16string GetStepTitle() const override;
+  std::u16string GetStepDescription() const override;
+  bool IsAcceptButtonVisible() const override;
+  bool IsAcceptButtonEnabled() const override;
+  bool IsCancelButtonVisible() const override;
+  std::u16string GetAcceptButtonLabel() const override;
+  void OnAccept() override;
+
+  bool touch_id_completed_ = false;
+};
+
 #endif  // IS_MAC
 
 class AuthenticatorOffTheRecordInterstitialSheetModel
diff --git a/chrome/browser/ui/webauthn/sheet_models_unittest.cc b/chrome/browser/ui/webauthn/sheet_models_unittest.cc
index 046c0e3c..1a4d4f5 100644
--- a/chrome/browser/ui/webauthn/sheet_models_unittest.cc
+++ b/chrome/browser/ui/webauthn/sheet_models_unittest.cc
@@ -102,7 +102,7 @@
 TEST_F(AuthenticatorMultiSourcePickerSheetModelTest, GPMPasskeysOnly) {
   AuthenticatorRequestDialogModel dialog_model(/*render_frame_host=*/nullptr);
   dialog_model.paired_phone_names = {base::UTF16ToUTF8(kPhoneName)};
-  dialog_model.priority_phone_index = 0;
+  dialog_model.priority_phone_name = dialog_model.paired_phone_names.at(0);
   dialog_model.mechanisms.emplace_back(
       Mechanism::Credential({device::AuthenticatorType::kPhone, {0}}),
       kPasskeyName1, kPasskeyName1, kPasskeyPhoneIcon, base::DoNothing());
@@ -124,7 +124,7 @@
 TEST_F(AuthenticatorMultiSourcePickerSheetModelTest, GPMAndLocalPasskeys) {
   AuthenticatorRequestDialogModel dialog_model(/*render_frame_host=*/nullptr);
   dialog_model.paired_phone_names = {base::UTF16ToUTF8(kPhoneName)};
-  dialog_model.priority_phone_index = 0;
+  dialog_model.priority_phone_name = dialog_model.paired_phone_names.at(0);
   dialog_model.mechanisms.emplace_back(
       Mechanism::Credential({device::AuthenticatorType::kPhone, {0}}),
       kPasskeyName1, kPasskeyName1, kPasskeyPhoneIcon, base::DoNothing());
diff --git a/chrome/browser/ui/webui/ash/app_install/app_install.mojom b/chrome/browser/ui/webui/ash/app_install/app_install.mojom
index 3aadb30..16707d6e 100644
--- a/chrome/browser/ui/webui/ash/app_install/app_install.mojom
+++ b/chrome/browser/ui/webui/ash/app_install/app_install.mojom
@@ -8,15 +8,17 @@
 import "url/mojom/url.mojom";
 
 struct DialogArgs {
-  url.mojom.Url url;
-
   string name;
 
+  url.mojom.Url url;
+
   string description;
 
   url.mojom.Url icon_url;
 
   array<Screenshot> screenshots;
+
+  bool is_already_installed;
 };
 
 struct Screenshot {
diff --git a/chrome/browser/ui/webui/ash/app_install/app_install_dialog.cc b/chrome/browser/ui/webui/ash/app_install/app_install_dialog.cc
index e9acac1..5951428 100644
--- a/chrome/browser/ui/webui/ash/app_install/app_install_dialog.cc
+++ b/chrome/browser/ui/webui/ash/app_install/app_install_dialog.cc
@@ -11,6 +11,7 @@
 #include "ash/style/typography.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
+#include "chrome/browser/apps/app_service/package_id_util.h"
 #include "chrome/browser/ui/webui/ash/app_install/app_install.mojom.h"
 #include "chrome/browser/ui/webui/ash/app_install/app_install_page_handler.h"
 #include "chrome/common/webui_url_constants.h"
@@ -58,10 +59,14 @@
 void AppInstallDialog::ShowApp(
     Profile* profile,
     gfx::NativeWindow parent,
-    mojom::DialogArgsPtr args,
+    apps::PackageId package_id,
+    std::string app_name,
+    GURL app_url,
+    std::string app_description,
+    GURL icon_url,
     int icon_width,
     bool is_icon_maskable,
-    std::string expected_app_id,
+    std::vector<mojom::ScreenshotPtr> screenshots,
     base::OnceCallback<void(bool accepted)> dialog_accepted_callback) {
   profile_ = profile->GetWeakPtr();
 
@@ -70,24 +75,27 @@
   }
   parent_ = std::move(parent);
 
-  expected_app_id_ = std::move(expected_app_id);
-  dialog_accepted_callback_ = std::move(dialog_accepted_callback);
+  package_id_ = std::move(package_id);
+  dialog_args_ = ash::app_install::mojom::DialogArgs::New();
+  dialog_args_->url = std::move(app_url);
+  dialog_args_->name = std::move(app_name);
+  dialog_args_->description = base::UTF16ToUTF8(gfx::TruncateString(
+      base::UTF8ToUTF16(app_description), webapps::kMaximumDescriptionLength,
+      gfx::CHARACTER_BREAK));
+  dialog_args_->icon_url = std::move(icon_url);
 
   // Filter out portrait screenshots.
-  std::vector<mojom::ScreenshotPtr> filtered_screenshots;
-  for (const auto& screenshot : args->screenshots) {
-    if (screenshot->size.width() >= screenshot->size.height() &&
-        screenshot->size.width() != 0) {
-      filtered_screenshots.push_back(screenshot.Clone());
-    }
-  }
-  args->screenshots = std::move(filtered_screenshots);
+  dialog_args_->screenshots = std::move(screenshots);
+  std::erase_if(dialog_args_->screenshots,
+                [](const mojom::ScreenshotPtr& screenshot) {
+                  return screenshot->size.width() < screenshot->size.height() ||
+                         screenshot->size.width() == 0;
+                });
 
-  args->description = base::UTF16ToUTF8(gfx::TruncateString(
-      base::UTF8ToUTF16(args->description), webapps::kMaximumDescriptionLength,
-      gfx::CHARACTER_BREAK));
+  dialog_args_->is_already_installed =
+      apps_util::GetAppWithPackageId(&*profile_, package_id_).has_value();
 
-  dialog_args_ = std::move(args);
+  dialog_accepted_callback_ = std::move(dialog_accepted_callback);
 
   icon_cache_ =
       std::make_unique<apps::AlmanacIconCache>(profile_.get()->GetProfileKey());
@@ -134,16 +142,17 @@
   this->RepositionNearTopOf(parent);
 }
 
-void AppInstallDialog::SetInstallSucceeded(const std::string* app_id) {
+void AppInstallDialog::SetInstallSucceeded() {
   if (dialog_ui_) {
-    dialog_ui_->SetInstallComplete(app_id, std::nullopt);
+    dialog_ui_->SetInstallComplete(/*success=*/true, std::nullopt);
   }
 }
 
 void AppInstallDialog::SetInstallFailed(
     base::OnceCallback<void(bool accepted)> retry_callback) {
   if (dialog_ui_) {
-    dialog_ui_->SetInstallComplete(nullptr, std::move(retry_callback));
+    dialog_ui_->SetInstallComplete(/*success=*/false,
+                                   std::move(retry_callback));
   }
 }
 
@@ -154,7 +163,7 @@
   SystemWebDialogDelegate::OnDialogShown(webui);
   dialog_ui_ = static_cast<AppInstallDialogUI*>(webui->GetController());
   dialog_ui_->SetDialogArgs(dialog_args_.Clone());
-  dialog_ui_->SetExpectedAppId(std::move(expected_app_id_));
+  dialog_ui_->SetPackageId(package_id_);
   dialog_ui_->SetDialogCallback(std::move(dialog_accepted_callback_));
   dialog_ui_->SetTryAgainCallback(std::move(try_again_callback_));
 }
@@ -204,6 +213,7 @@
 }
 
 namespace {
+constexpr int kNoAppDataHeight = 228;
 constexpr int kMinimumDialogHeight = 282;
 constexpr int kDescriptionContainerWidth = 408;
 constexpr int kDescriptionLineHeight = 18;
@@ -213,9 +223,10 @@
 }  // namespace
 
 void AppInstallDialog::GetDialogSize(gfx::Size* size) const {
-  int height = kMinimumDialogHeight;
+  int height = 0;
 
   if (dialog_args_) {
+    height += kMinimumDialogHeight;
     // TODO(b/329515116): Adjust height for long URLs that wrap multiple
     // lines.
     if (dialog_args_->description.length()) {
@@ -244,6 +255,8 @@
       // The description padding is there even when there is no description.
       height += kDescriptionVerticalPadding;
     }
+  } else {
+    height += kNoAppDataHeight;
   }
 
   size->SetSize(SystemWebDialogDelegate::kDialogWidth, height);
diff --git a/chrome/browser/ui/webui/ash/app_install/app_install_dialog.h b/chrome/browser/ui/webui/ash/app_install/app_install_dialog.h
index 6d76d7f..d89e0189 100644
--- a/chrome/browser/ui/webui/ash/app_install/app_install_dialog.h
+++ b/chrome/browser/ui/webui/ash/app_install/app_install_dialog.h
@@ -10,6 +10,7 @@
 #include "chrome/browser/ui/webui/ash/app_install/app_install_ui.h"
 #include "chrome/browser/ui/webui/ash/system_web_dialog_delegate.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
+#include "components/services/app_service/public/cpp/package_id.h"
 #include "ui/views/native_window_tracker.h"
 
 namespace ash::app_install {
@@ -33,10 +34,14 @@
   void ShowApp(
       Profile* profile,
       gfx::NativeWindow parent,
-      mojom::DialogArgsPtr args,
+      apps::PackageId package_id,
+      std::string app_name,
+      GURL app_url,
+      std::string app_description,
+      GURL icon_url,
       int icon_width,
       bool is_icon_maskable,
-      std::string expected_app_id,
+      std::vector<mojom::ScreenshotPtr> screenshots,
       base::OnceCallback<void(bool accepted)> dialog_accepted_callback);
 
   // Displays the dialog with an error message that there's no app info.
@@ -51,7 +56,7 @@
   // Callers must call one of SetInstallSucceeded or SetInstallFailed once the
   // install has finished, passing in the app_id if the installation succeeded
   // or a callback to retry the install if it failed.
-  void SetInstallSucceeded(const std::string* app_id);
+  void SetInstallSucceeded();
   void SetInstallFailed(base::OnceCallback<void(bool accepted)> retry_callback);
 
   void OnDialogShown(content::WebUI* webui) override;
@@ -73,8 +78,8 @@
 
   base::WeakPtr<Profile> profile_;
   gfx::NativeWindow parent_;
+  apps::PackageId package_id_;
   mojom::DialogArgsPtr dialog_args_;
-  std::string expected_app_id_;
 
   std::unique_ptr<views::NativeWindowTracker> parent_window_tracker_;
   std::unique_ptr<apps::AlmanacIconCache> icon_cache_;
diff --git a/chrome/browser/ui/webui/ash/app_install/app_install_dialog_browsertest.cc b/chrome/browser/ui/webui/ash/app_install/app_install_dialog_browsertest.cc
index 03c17827..2d59d30f 100644
--- a/chrome/browser/ui/webui/ash/app_install/app_install_dialog_browsertest.cc
+++ b/chrome/browser/ui/webui/ash/app_install/app_install_dialog_browsertest.cc
@@ -4,10 +4,14 @@
 
 #include "chrome/browser/ui/webui/ash/app_install/app_install_dialog.h"
 
+#include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/apps/almanac_api_client/almanac_api_util.h"
+#include "chrome/browser/apps/app_service/app_install/app_install.pb.h"
 #include "chrome/browser/apps/app_service/app_install/app_install_service.h"
+#include "chrome/browser/apps/app_service/app_registry_cache_waiter.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ui/browser.h"
@@ -15,6 +19,7 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/web_app_dialog_utils.h"
+#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_install_params.h"
@@ -58,6 +63,43 @@
         {});
   }
 
+  void SetUpOnMainThread() override {
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &AppInstallDialogBrowserTest::HandleRequest, base::Unretained(this)));
+    ASSERT_TRUE(embedded_test_server()->Start());
+
+    apps::SetAlmanacEndpointUrlForTesting(
+        embedded_test_server()->GetURL("/").spec());
+  }
+
+  std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+      const net::test_server::HttpRequest& request) {
+    auto it = response_map_.find(request.GetURL());
+    if (it == response_map_.end()) {
+      return nullptr;
+    }
+    auto http_response =
+        std::make_unique<net::test_server::BasicHttpResponse>();
+    http_response->set_code(net::HTTP_OK);
+    http_response->set_content(it->second);
+    return std::move(http_response);
+  }
+
+  void SetUpAlmanacPayload(const char* app_url) {
+    apps::proto::AppInstallResponse response;
+    apps::proto::AppInstallResponse_AppInstance& instance =
+        *response.mutable_app_instance();
+    instance.set_package_id(base::StrCat({"web:", app_url}));
+    instance.set_name("Test app");
+    apps::proto::AppInstallResponse_WebExtras& web_extras =
+        *instance.mutable_web_extras();
+    web_extras.set_document_url(app_url);
+    web_extras.set_original_manifest_url(app_url);
+    web_extras.set_scs_url(app_url);
+    response_map_[embedded_test_server()->GetURL("/v1/app-install")] =
+        response.SerializeAsString();
+  }
+
   std::string GetTitle(content::WebContents* web_contents) {
     return content::EvalJs(web_contents, R"(
       document.querySelector('app-install-dialog').shadowRoot
@@ -81,12 +123,12 @@
     )");
   }
 
- private:
+ protected:
+  std::map<GURL, std::string> response_map_;
   base::test::ScopedFeatureList feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(AppInstallDialogBrowserTest, InstallApp) {
-  ASSERT_TRUE(embedded_test_server()->Start());
   const GURL app_url(embedded_test_server()->GetURL("/web_apps/basic.html"));
 
   ui_test_utils::NavigateToURLWithDispositionBlockUntilNavigationsComplete(
@@ -108,6 +150,8 @@
   EXPECT_TRUE(base::StartsWith(GetTitle(web_contents), "Install app on your"));
 
   // Click the install button.
+  while (GetActionButton(web_contents) != "Install")
+    ;
   EXPECT_TRUE(ClickActionButton(web_contents));
 
   // Make sure the button goes through the 'Installing' state.
@@ -137,6 +181,46 @@
       app_url);
 }
 
+IN_PROC_BROWSER_TEST_F(AppInstallDialogBrowserTest, AlreadyInstalled) {
+  constexpr char kAppUrl[] = "https://example.org/";
+  webapps::AppId app_id = web_app::GenerateAppIdFromManifestId(GURL(kAppUrl));
+
+  SetUpAlmanacPayload(kAppUrl);
+
+  web_app::test::InstallDummyWebApp(browser()->profile(), "Test app",
+                                    GURL(kAppUrl));
+  apps::AppReadinessWaiter(browser()->profile(), app_id).Await();
+
+  content::TestNavigationObserver navigation_observer_dialog(
+      (GURL(chrome::kChromeUIAppInstallDialogURL)));
+  navigation_observer_dialog.StartWatchingNewWebContents();
+
+  auto* proxy =
+      apps::AppServiceProxyFactory::GetForProfile(browser()->profile());
+  proxy->AppInstallService().InstallApp(
+      apps::AppInstallSurface::kAppInstallUriUnknown,
+      apps::PackageId(apps::PackageType::kWeb, kAppUrl),
+      /*anchor_window=*/std::nullopt,
+      /*callback=*/base::DoNothing());
+
+  navigation_observer_dialog.Wait();
+  ASSERT_TRUE(navigation_observer_dialog.last_navigation_succeeded());
+
+  content::WebContents* web_contents = GetWebContentsFromDialog();
+
+  EXPECT_EQ(GetTitle(web_contents), "App is already installed");
+  EXPECT_EQ(GetActionButton(web_contents), "Open app");
+
+  // Click the open app button and expect the dialog was closed.
+  content::WebContentsDestroyedWatcher watcher(web_contents);
+  EXPECT_TRUE(ClickActionButton(web_contents));
+  watcher.Wait();
+
+  // Expect the app is opened.
+  Browser* app_browser = BrowserList::GetInstance()->GetLastActive();
+  EXPECT_TRUE(web_app::AppBrowserController::IsForWebApp(app_browser, app_id));
+}
+
 IN_PROC_BROWSER_TEST_F(AppInstallDialogBrowserTest, FailedInstall) {
   content::TestNavigationObserver navigation_observer_dialog(
       (GURL(chrome::kChromeUIAppInstallDialogURL)));
@@ -146,13 +230,18 @@
       AppInstallDialog::CreateDialog();
 
   // TODO(b/331310950): Add a test that sends a retry callback.
-  auto args = ash::app_install::mojom::DialogArgs::New();
-  args->url = GURL("https://example.org");
+  constexpr char kAppUrl[] = "https://example.org/";
   dialog_handle->ShowApp(
-      browser()->profile(), browser()->window()->GetNativeWindow(),
-      /* dialog_args= */ std::move(args),
-      /* icon_width= */ 0, /* is_icon_maskable= */ true,
-      /* expected_app_id= */ "",
+      browser()->profile(),
+      /*parent=*/browser()->window()->GetNativeWindow(),
+      apps::PackageId(apps::PackageType::kWeb, kAppUrl),
+      /*app_name=*/"Test app",
+      /*app_url=*/GURL(kAppUrl),
+      /*app_description=*/"",
+      /*icon_url=*/GURL(),
+      /*icon_width=*/0,
+      /*is_icon_maskable=*/false,
+      /*screenshots=*/{},
       base::BindOnce(
           [](base::WeakPtr<AppInstallDialog> dialog_handle,
              bool dialog_accepted) {
@@ -166,6 +255,8 @@
   content::WebContents* web_contents = GetWebContentsFromDialog();
 
   // Click the install button.
+  while (GetActionButton(web_contents) != "Install")
+    ;
   EXPECT_TRUE(ClickActionButton(web_contents));
 
   // Make sure the button goes through the 'Installing' state.
@@ -188,7 +279,7 @@
       apps::AppServiceProxyFactory::GetForProfile(browser()->profile());
   proxy->AppInstallService().InstallApp(
       apps::AppInstallSurface::kAppInstallUriUnknown,
-      apps::PackageId(apps::AppType::kWeb, "invalid"),
+      apps::PackageId(apps::PackageType::kWeb, "invalid"),
       /*anchor_window=*/std::nullopt,
       /*callback=*/base::DoNothing());
 
@@ -197,7 +288,7 @@
 
   content::WebContents* web_contents = GetWebContentsFromDialog();
 
-  EXPECT_EQ(GetTitle(web_contents), "Could not download app data");
+  EXPECT_EQ(GetTitle(web_contents), "Can't install this app");
   EXPECT_EQ(GetActionButton(web_contents), "Try again");
 }
 
diff --git a/chrome/browser/ui/webui/ash/app_install/app_install_page_handler.cc b/chrome/browser/ui/webui/ash/app_install/app_install_page_handler.cc
index 8cffd6e..1aa7a486 100644
--- a/chrome/browser/ui/webui/ash/app_install/app_install_page_handler.cc
+++ b/chrome/browser/ui/webui/ash/app_install/app_install_page_handler.cc
@@ -9,12 +9,15 @@
 #include "base/metrics/user_metrics.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/apps/app_service/package_id_util.h"
 #include "chrome/browser/metrics/structured/event_logging_features.h"
 #include "chrome/browser/ui/webui/ash/app_install/app_install.mojom.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "components/metrics/structured/structured_events.h"
 #include "components/metrics/structured/structured_metrics_client.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
+#include "components/services/app_service/public/cpp/app_types.h"
 
 namespace ash::app_install {
 
@@ -28,6 +31,14 @@
 
 bool g_auto_accept_for_testing = false;
 
+// TODO(b/330414871): AppInstallService shouldn't know about publisher specific
+// logic, remove the generation of app_ids.
+std::string GetAppId(const apps::PackageId& package_id) {
+  CHECK_EQ(package_id.package_type(), apps::PackageType::kWeb);
+  // data->package_id.identifier() is the manifest ID for web apps.
+  return web_app::GenerateAppIdFromManifestId(GURL(package_id.identifier()));
+}
+
 }  // namespace
 
 // static
@@ -43,14 +54,14 @@
 AppInstallPageHandler::AppInstallPageHandler(
     Profile* profile,
     mojom::DialogArgsPtr args,
-    std::string expected_app_id,
+    apps::PackageId package_id,
     base::OnceCallback<void(bool accepted)> dialog_accepted_callback,
     CloseDialogCallback close_dialog_callback,
     base::OnceClosure try_again_callback,
     mojo::PendingReceiver<mojom::PageHandler> pending_page_handler)
     : profile_{profile},
       dialog_args_{std::move(args)},
-      expected_app_id_(expected_app_id),
+      package_id_{std::move(package_id)},
       dialog_accepted_callback_{std::move(dialog_accepted_callback)},
       close_dialog_callback_{std::move(close_dialog_callback)},
       try_again_callback_{std::move(try_again_callback)},
@@ -81,7 +92,9 @@
           std::move(cros_events::AppDiscovery_Browser_AppInstallDialogResult()
                         .SetWebAppInstallStatus(
                             ToLong(web_app::WebAppInstallStatus::kCancelled))
-                        .SetAppId(expected_app_id_)));
+                        // TODO(b/333643533): This should be using
+                        // AppDiscoveryMetrics::GetAppStringToRecord().
+                        .SetAppId(GetAppId(package_id_))));
     }
     std::move(dialog_accepted_callback_).Run(false);
   }
@@ -101,7 +114,9 @@
         std::move(cros_events::AppDiscovery_Browser_AppInstallDialogResult()
                       .SetWebAppInstallStatus(
                           ToLong(web_app::WebAppInstallStatus::kAccepted))
-                      .SetAppId(expected_app_id_)));
+                      // TODO(b/333643533): This should be using
+                      // AppDiscoveryMetrics::GetAppStringToRecord().
+                      .SetAppId(GetAppId(package_id_))));
   }
 
   install_app_callback_ = std::move(callback);
@@ -109,32 +124,28 @@
 }
 
 void AppInstallPageHandler::OnInstallComplete(
-    const std::string* app_id,
+    bool success,
     std::optional<base::OnceCallback<void(bool accepted)>> retry_callback) {
-  if (app_id) {
-    app_id_ = *app_id;
-    // OnInstallComplete must not be called with an 'app_id' if the expected app
-    // was not able to be installed. The app_id must match also the expected app
-    // id.
-    CHECK_EQ(*app_id, expected_app_id_);
-  } else {
+  if (!success) {
     CHECK(retry_callback.has_value());
     dialog_accepted_callback_ = std::move(retry_callback.value());
   }
   if (install_app_callback_) {
-    std::move(install_app_callback_).Run(/*success=*/app_id);
+    std::move(install_app_callback_).Run(success);
   }
 }
 
 void AppInstallPageHandler::LaunchApp() {
-  if (app_id_.empty()) {
+  std::optional<std::string> app_id =
+      apps_util::GetAppWithPackageId(&*profile_, package_id_);
+  if (!app_id.has_value()) {
     mojo::ReportBadMessage("Unable to launch app without an app_id.");
     return;
   }
   base::RecordAction(
       base::UserMetricsAction("ChromeOS.AppInstallDialog.AppLaunched"));
   apps::AppServiceProxyFactory::GetForProfile(profile_)->Launch(
-      app_id_, ui::EF_NONE, apps::LaunchSource::kFromInstaller);
+      app_id.value(), ui::EF_NONE, apps::LaunchSource::kFromInstaller);
 }
 
 void AppInstallPageHandler::TryAgain() {
diff --git a/chrome/browser/ui/webui/ash/app_install/app_install_page_handler.h b/chrome/browser/ui/webui/ash/app_install/app_install_page_handler.h
index 25ffd2b..5df2a83 100644
--- a/chrome/browser/ui/webui/ash/app_install/app_install_page_handler.h
+++ b/chrome/browser/ui/webui/ash/app_install/app_install_page_handler.h
@@ -5,12 +5,11 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_ASH_APP_INSTALL_APP_INSTALL_PAGE_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_ASH_APP_INSTALL_APP_INSTALL_PAGE_HANDLER_H_
 
-#include <string>
-
 #include "base/functional/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/ash/app_install/app_install.mojom.h"
+#include "components/services/app_service/public/cpp/package_id.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -31,7 +30,7 @@
   explicit AppInstallPageHandler(
       Profile* profile,
       mojom::DialogArgsPtr args,
-      std::string expected_app_id,
+      apps::PackageId package_id,
       base::OnceCallback<void(bool accepted)> dialog_accepted_callback,
       CloseDialogCallback close_dialog_callback,
       base::OnceClosure try_again_callback,
@@ -43,7 +42,7 @@
   ~AppInstallPageHandler() override;
 
   void OnInstallComplete(
-      const std::string* app_id,
+      bool success,
       std::optional<base::OnceCallback<void(bool accepted)>> retry_callback);
 
   // mojom::PageHandler:
@@ -56,13 +55,12 @@
  private:
   raw_ptr<Profile> profile_;
   mojom::DialogArgsPtr dialog_args_;
-  std::string expected_app_id_;
+  apps::PackageId package_id_;
   base::OnceCallback<void(bool accepted)> dialog_accepted_callback_;
   InstallAppCallback install_app_callback_;
   CloseDialogCallback close_dialog_callback_;
   base::OnceClosure try_again_callback_;
   mojo::Receiver<mojom::PageHandler> receiver_;
-  std::string app_id_;
 
   base::WeakPtrFactory<AppInstallPageHandler> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/ui/webui/ash/app_install/app_install_ui.cc b/chrome/browser/ui/webui/ash/app_install/app_install_ui.cc
index 5dfb857..511a95c 100644
--- a/chrome/browser/ui/webui/ash/app_install/app_install_ui.cc
+++ b/chrome/browser/ui/webui/ash/app_install/app_install_ui.cc
@@ -40,7 +40,10 @@
       {"appDetails", IDS_APP_INSTALL_DIALOG_APP_DETAILS_TEXT},
       {"installingApp", IDS_APP_INSTALL_DIALOG_INSTALLING_APP_TITLE},
       {"appInstalled", IDS_APP_INSTALL_DIALOG_APP_INSTALLED_TITLE},
-      {"noAppData", IDS_APP_INSTALL_DIALOG_NO_APP_DATA_TITLE},
+      {"appAlreadyInstalled",
+       IDS_APP_INSTALL_DIALOG_APP_ALREADY_INSTALLED_TITLE},
+      {"noAppDataTitle", IDS_APP_INSTALL_DIALOG_NO_APP_DATA_TITLE},
+      {"noAppDataDescription", IDS_APP_INSTALL_DIALOG_NO_APP_DATA_DESCRIPTION},
       {"tryAgain", IDS_APP_INSTALL_DIALOG_TRY_AGAIN_BUTTON_LABEL},
       {"failedInstall", IDS_APP_INSTALL_DIALOG_FAILED_INSTALL_TITLE},
   };
@@ -69,8 +72,8 @@
   dialog_args_ = std::move(args);
 }
 
-void AppInstallDialogUI::SetExpectedAppId(std::string expected_app_id) {
-  expected_app_id_ = expected_app_id;
+void AppInstallDialogUI::SetPackageId(apps::PackageId package_id) {
+  package_id_ = std::move(package_id);
 }
 
 void AppInstallDialogUI::SetDialogCallback(
@@ -84,12 +87,12 @@
 }
 
 void AppInstallDialogUI::SetInstallComplete(
-    const std::string* app_id,
+    bool success,
     std::optional<base::OnceCallback<void(bool accepted)>> retry_callback) {
   if (!page_handler_) {
     return;
   }
-  page_handler_->OnInstallComplete(app_id, std::move(retry_callback));
+  page_handler_->OnInstallComplete(success, std::move(retry_callback));
 }
 
 void AppInstallDialogUI::BindInterface(
@@ -110,7 +113,7 @@
     mojo::PendingReceiver<mojom::PageHandler> receiver) {
   page_handler_ = std::make_unique<AppInstallPageHandler>(
       Profile::FromWebUI(web_ui()), std::move(dialog_args_),
-      std::move(expected_app_id_), std::move(dialog_accepted_callback_),
+      std::move(package_id_), std::move(dialog_accepted_callback_),
       base::BindOnce(&AppInstallDialogUI::CloseDialog, base::Unretained(this)),
       std::move(try_again_callback_), std::move(receiver));
 }
diff --git a/chrome/browser/ui/webui/ash/app_install/app_install_ui.h b/chrome/browser/ui/webui/ash/app_install/app_install_ui.h
index 8c846dd..9d98951 100644
--- a/chrome/browser/ui/webui/ash/app_install/app_install_ui.h
+++ b/chrome/browser/ui/webui/ash/app_install/app_install_ui.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_WEBUI_ASH_APP_INSTALL_APP_INSTALL_UI_H_
 
 #include "chrome/browser/ui/webui/ash/app_install/app_install_page_handler.h"
+#include "components/services/app_service/public/cpp/package_id.h"
 #include "content/public/browser/webui_config.h"
 #include "ui/web_dialogs/web_dialog_ui.h"
 #include "ui/webui/resources/cr_components/color_change_listener/color_change_listener.mojom.h"
@@ -27,12 +28,12 @@
   ~AppInstallDialogUI() override;
 
   void SetDialogArgs(mojom::DialogArgsPtr args);
-  void SetExpectedAppId(std::string expected_app_id);
+  void SetPackageId(apps::PackageId package_id);
   void SetDialogCallback(
       base::OnceCallback<void(bool accepted)> dialog_accepted_callback);
   void SetTryAgainCallback(base::OnceClosure try_again_callback);
   void SetInstallComplete(
-      const std::string* app_id,
+      bool success,
       std::optional<base::OnceCallback<void(bool accepted)>> retry_callback);
 
   // Instantiates the implementor of the mojom::PageHandlerFactory mojo
@@ -51,7 +52,7 @@
   void CloseDialog();
 
   mojom::DialogArgsPtr dialog_args_;
-  std::string expected_app_id_;
+  apps::PackageId package_id_;
   base::OnceCallback<void(bool accepted)> dialog_accepted_callback_;
   base::OnceClosure try_again_callback_;
   std::unique_ptr<AppInstallPageHandler> page_handler_;
diff --git a/chrome/browser/ui/webui/ash/mako/mako_ui.cc b/chrome/browser/ui/webui/ash/mako/mako_ui.cc
index 78e5dc07..08eca828 100644
--- a/chrome/browser/ui/webui/ash/mako/mako_ui.cc
+++ b/chrome/browser/ui/webui/ash/mako/mako_ui.cc
@@ -23,6 +23,17 @@
 #include "content/public/common/url_constants.h"
 
 namespace ash {
+namespace {
+
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+constexpr int kEnUSResourceIds[] = {IDR_MAKO_ORCA_HTML, IDR_MAKO_PRIVACY_HTML,
+                                    IDR_MAKO_ORCA_JS,
+                                    IDR_MAKO_ORCA_TRANSLATION_EN_JS};
+#else
+constexpr int kEnUSResourceIds[] = {IDR_MAKO_ORCA_HTML, IDR_MAKO_PRIVACY_HTML,
+                                    IDR_MAKO_ORCA_JS, IDR_MAKO_ORCA_EN};
+#endif
+} // namespace
 
 MakoUntrustedUIConfig::MakoUntrustedUIConfig()
     : WebUIConfig(content::kChromeUIUntrustedScheme, ash::kChromeUIMakoHost) {}
@@ -47,9 +58,24 @@
   // Setup the data source
   content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
       web_ui->GetWebContents()->GetBrowserContext(), kChromeUIMakoURL);
-  webui::SetupWebUIDataSource(
-      source, base::make_span(kOrcaResources, kOrcaResourcesSize),
-      IDR_MAKO_ORCA_HTML);
+
+  base::span<const webui::ResourcePath> orca_resources =
+      base::make_span(kOrcaResources, kOrcaResourcesSize);
+
+  // TODO: b:333625296 - Add tests for this conditional behavior
+  if (chromeos::features::IsOrcaUseL10nStringsEnabled()) {
+    webui::SetupWebUIDataSource(source, orca_resources, IDR_MAKO_ORCA_HTML);
+  } else {
+    std::vector<webui::ResourcePath> orca_en_us_resources;
+    std::copy_if(orca_resources.begin(), orca_resources.end(),
+                 std::back_inserter(orca_en_us_resources),
+                 [](const webui::ResourcePath& resource_path) {
+                   return base::Contains(kEnUSResourceIds, resource_path.id);
+                 });
+    webui::SetupWebUIDataSource(source, base::make_span(orca_en_us_resources),
+                                IDR_MAKO_ORCA_HTML);
+  }
+
   source->SetDefaultResource(IDR_MAKO_ORCA_HTML);
 
   // Setup additional CSP overrides
diff --git a/chrome/browser/ui/webui/ash/office_fallback/office_fallback_ui.cc b/chrome/browser/ui/webui/ash/office_fallback/office_fallback_ui.cc
index b057261..b7c4beb 100644
--- a/chrome/browser/ui/webui/ash/office_fallback/office_fallback_ui.cc
+++ b/chrome/browser/ui/webui/ash/office_fallback/office_fallback_ui.cc
@@ -41,7 +41,6 @@
        IDS_OFFICE_FALLBACK_OPEN_IN_BASIC_EDITOR},
   };
   source->AddLocalizedStrings(kStrings);
-  source->AddBoolean("isJellyEnabled", chromeos::features::IsJellyEnabled());
   webui::SetupWebUIDataSource(
       source,
       base::make_span(kOfficeFallbackResources, kOfficeFallbackResourcesSize),
diff --git a/chrome/browser/ui/webui/ash/settings/pages/device/inputs_section.cc b/chrome/browser/ui/webui/ash/settings/pages/device/inputs_section.cc
index 052c22ed..8bf9ff6 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/device/inputs_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/device/inputs_section.cc
@@ -479,8 +479,10 @@
   html_source->AddBoolean(
       "onDeviceGrammarCheckEnabled",
       base::FeatureList::IsEnabled(features::kOnDeviceGrammarCheck));
-  html_source->AddBoolean("languagePacksHandwritingEnabled",
-                          features::IsLanguagePacksEnabled());
+
+  // TODO: b/332967598 - Remove obsolete pre-LanguagePacks code in Settings.
+  html_source->AddBoolean("languagePacksHandwritingEnabled", true);
+
   html_source->AddBoolean(
       "systemJapanesePhysicalTyping",
       base::FeatureList::IsEnabled(features::kSystemJapanesePhysicalTyping));
diff --git a/chrome/browser/ui/webui/ash/settings/pages/personalization/personalization_section.cc b/chrome/browser/ui/webui/ash/settings/pages/personalization/personalization_section.cc
index bb9d647..a9c8bf46 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/personalization/personalization_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/personalization/personalization_section.cc
@@ -63,16 +63,22 @@
 
 void PersonalizationSection::AddLoadTimeData(
     content::WebUIDataSource* html_source) {
+  const bool kIsGuest = IsGuestModeActive();
+
   webui::LocalizedString kWallpaperLocalizedStrings[] = {
       {"personalizationPageTitle", isRevampEnabled_
                                        ? IDS_OS_SETTINGS_REVAMP_PERSONALIZATION
                                        : IDS_OS_SETTINGS_PERSONALIZATION},
       {"personalizationMenuItemDescription",
-       IDS_OS_SETTINGS_PERSONALIZATION_MENU_ITEM_DESCRIPTION},
+       kIsGuest
+           ? IDS_OS_SETTINGS_PERSONALIZATION_MENU_ITEM_DESCRIPTION_GUEST_MODE
+           : IDS_OS_SETTINGS_PERSONALIZATION_MENU_ITEM_DESCRIPTION},
       {"personalizationHubTitle", IDS_OS_SETTINGS_OPEN_PERSONALIZATION_HUB},
       {"personalizationHubSubtitle",
        isRevampEnabled_
-           ? IDS_OS_SETTINGS_REVAMP_OPEN_PERSONALIZATION_HUB_SUBTITLE
+           ? (kIsGuest
+                  ? IDS_OS_SETTINGS_REVAMP_OPEN_PERSONALIZATION_HUB_SUBTITLE_GUEST_MODE
+                  : IDS_OS_SETTINGS_REVAMP_OPEN_PERSONALIZATION_HUB_SUBTITLE)
            : IDS_OS_SETTINGS_OPEN_PERSONALIZATION_HUB_SUBTITLE},
   };
 
diff --git a/chrome/browser/ui/webui/ash/settings/pages/personalization/personalization_section_unittest.cc b/chrome/browser/ui/webui/ash/settings/pages/personalization/personalization_section_unittest.cc
new file mode 100644
index 0000000..3d659fca
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/settings/pages/personalization/personalization_section_unittest.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 "chrome/browser/ui/webui/ash/settings/pages/personalization/personalization_section.h"
+
+#include "ash/constants/ash_features.h"
+#include "base/memory/raw_ptr.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/ui/webui/ash/settings/os_settings_identifier.h"
+#include "chrome/browser/ui/webui/ash/settings/search/search_tag_registry.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/user_manager/fake_user_manager.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_web_ui_data_source.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash::settings {
+
+// Test for the device settings page.
+class PersonalizationSectionTest : public testing::Test {
+ public:
+  PersonalizationSectionTest()
+      : local_search_service_proxy_(
+            std::make_unique<
+                ash::local_search_service::LocalSearchServiceProxy>(
+                /*for_testing=*/true)),
+        search_tag_registry_(local_search_service_proxy_.get()) {}
+  ~PersonalizationSectionTest() override = default;
+
+ protected:
+  void SetUp() override {
+    feature_list_.InitAndEnableFeature(
+        ash::features::kOsSettingsRevampWayfinding);
+
+    ASSERT_TRUE(test_profile_manager_.SetUp());
+    user_manager_ = std::make_unique<FakeChromeUserManager>();
+    user_manager_->Initialize();
+    task_environment_.RunUntilIdle();
+
+    profile_ =
+        test_profile_manager_.CreateTestingProfile("test-user@example.com");
+    section_ = std::make_unique<PersonalizationSection>(
+        profile_, &search_tag_registry_, &pref_service_);
+  }
+
+  void TearDown() override {
+    section_.reset();
+    profile_ = nullptr;
+    user_manager_->Shutdown();
+    user_manager_->Destroy();
+    user_manager_.reset();
+    test_profile_manager_.DeleteAllTestingProfiles();
+  }
+
+  void LoginUser() {
+    const AccountId account_id(
+        AccountId::FromUserEmail(profile_->GetProfileUserName()));
+    user_manager_->AddUser(account_id);
+    user_manager_->LoginUser(account_id);
+    user_manager_->SwitchActiveUser(account_id);
+    task_environment_.RunUntilIdle();
+  }
+
+  void LoginGuestUser() {
+    user_manager::User* guest_user = user_manager_->AddGuestUser();
+    const AccountId account_id = guest_user->GetAccountId();
+    test_profile_manager_.CreateTestingProfile(account_id.GetUserEmail());
+    user_manager_->LoginUser(account_id);
+    user_manager_->SwitchActiveUser(account_id);
+    task_environment_.RunUntilIdle();
+  }
+
+  std::unique_ptr<PersonalizationSection> section_;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+  content::BrowserTaskEnvironment task_environment_;
+  std::unique_ptr<ash::local_search_service::LocalSearchServiceProxy>
+      local_search_service_proxy_;
+  ash::settings::SearchTagRegistry search_tag_registry_;
+  TestingPrefServiceSimple pref_service_;
+  raw_ptr<Profile> profile_;
+  std::unique_ptr<FakeChromeUserManager> user_manager_;
+  TestingProfileManager test_profile_manager_{
+      TestingBrowserProcess::GetGlobal()};
+};
+
+// Verify menu item description string in load time data.
+TEST_F(PersonalizationSectionTest, MenuItemDescriptionString) {
+  LoginUser();
+  std::unique_ptr<content::TestWebUIDataSource> html_source =
+      content::TestWebUIDataSource::Create(chrome::kChromeUIOSSettingsHost);
+  section_->AddLoadTimeData(html_source->GetWebUIDataSource());
+
+  EXPECT_EQ(std::string("Dark theme, screen saver"),
+            *html_source->GetLocalizedStrings()->FindString(
+                "personalizationMenuItemDescription"));
+}
+
+// Verify menu item description string in guest mode does not include "screen
+// saver", in load time data.
+TEST_F(PersonalizationSectionTest, MenuItemDescriptionStringGuestMode) {
+  LoginGuestUser();
+  std::unique_ptr<content::TestWebUIDataSource> html_source =
+      content::TestWebUIDataSource::Create(chrome::kChromeUIOSSettingsHost);
+  section_->AddLoadTimeData(html_source->GetWebUIDataSource());
+
+  EXPECT_EQ(std::string("Dark theme"),
+            *html_source->GetLocalizedStrings()->FindString(
+                "personalizationMenuItemDescription"));
+}
+
+// Verify row description string in load time data.
+TEST_F(PersonalizationSectionTest, RowDescriptionString) {
+  LoginUser();
+  std::unique_ptr<content::TestWebUIDataSource> html_source =
+      content::TestWebUIDataSource::Create(chrome::kChromeUIOSSettingsHost);
+  section_->AddLoadTimeData(html_source->GetWebUIDataSource());
+
+  EXPECT_EQ(
+      std::string("Personalize wallpaper, screen saver, dark theme, and more"),
+      *html_source->GetLocalizedStrings()->FindString(
+          "personalizationHubSubtitle"));
+}
+
+// Verify row description string in guest mode does not include "screen saver",
+// in load time data.
+TEST_F(PersonalizationSectionTest, RowDescriptionStringGuestMode) {
+  LoginGuestUser();
+  std::unique_ptr<content::TestWebUIDataSource> html_source =
+      content::TestWebUIDataSource::Create(chrome::kChromeUIOSSettingsHost);
+  section_->AddLoadTimeData(html_source->GetWebUIDataSource());
+
+  EXPECT_EQ(std::string("Personalize wallpaper, dark theme, and more"),
+            *html_source->GetLocalizedStrings()->FindString(
+                "personalizationHubSubtitle"));
+}
+
+}  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/browser_command/browser_command_handler.cc b/chrome/browser/ui/webui/browser_command/browser_command_handler.cc
index 2c6fab8..32fee0a 100644
--- a/chrome/browser/ui/webui/browser_command/browser_command_handler.cc
+++ b/chrome/browser/ui/webui/browser_command/browser_command_handler.cc
@@ -272,8 +272,7 @@
 
   // Duplicated from chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
   // Which cannot be included here
-  return base::FeatureList::IsEnabled(features::kTabGroupsSave) &&
-         browser->profile()->IsRegularProfile();
+  return browser->profile()->IsRegularProfile();
 }
 
 void BrowserCommandHandler::OpenNTPAndStartCustomizeChromeTutorial() {
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index 7a89ecdb..184294d 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -451,6 +451,7 @@
       {"modulesDriveInfo", IDS_NTP_MODULES_DRIVE_INFO},
       {"modulesDummyTitle", IDS_NTP_MODULES_DUMMY_TITLE},
       {"modulesFeedTitle", IDS_NTP_MODULES_FEED_TITLE},
+      {"modulesGoogleCalendarTitle", IDS_NTP_MODULES_GOOGLE_CALENDAR_TITLE},
       {"modulesKaleidoscopeTitle", IDS_NTP_MODULES_KALEIDOSCOPE_TITLE},
       {"modulesPhotosInfo", IDS_NTP_MODULES_PHOTOS_INFO},
       {"modulesPhotosSentence", IDS_NTP_MODULES_PHOTOS_MEMORIES_TITLE},
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index f206fc1..56e52ff 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -2764,8 +2764,6 @@
        IDS_SITE_SETTINGS_TYPE_AUTOMATIC_FULLSCREEN_MID_SENTENCE},
       {"siteSettingsAutomaticFullscreenDescription",
        IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_DESCRIPTION},
-      {"siteSettingsAutomaticFullscreenBlock",
-       IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_BLOCK},
       {"siteSettingsAutomaticFullscreenAllowedExceptions",
        IDS_SETTINGS_SITE_SETTINGS_AUTOMATIC_FULLSCREEN_ALLOWED_EXCEPTIONS},
       {"siteSettingsAutomaticFullscreenBlockedExceptions",
diff --git a/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc b/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
index 89fd384..3c28edce 100644
--- a/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
+++ b/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
@@ -692,7 +692,7 @@
   // Web App's publisher_id the start url.
   app->publisher_id = web_app->start_url().spec();
   app->installer_package_id =
-      apps::PackageId(apps::AppType::kWeb, web_app->manifest_id().spec());
+      apps::PackageId(apps::PackageType::kWeb, web_app->manifest_id().spec());
 
   app->icon_key = apps::IconKey(GetIconEffects(web_app));
 
diff --git a/chrome/browser/web_applications/os_integration/os_integration_manager.cc b/chrome/browser/web_applications/os_integration/os_integration_manager.cc
index af4f2ce5..78b888a 100644
--- a/chrome/browser/web_applications/os_integration/os_integration_manager.cc
+++ b/chrome/browser/web_applications/os_integration/os_integration_manager.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/os_integration/file_handling_sub_manager.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_sub_manager.h"
+#include "chrome/browser/web_applications/os_integration/os_integration_test_override.h"
 #include "chrome/browser/web_applications/os_integration/protocol_handling_sub_manager.h"
 #include "chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager.h"
 #include "chrome/browser/web_applications/os_integration/shortcut_menu_handling_sub_manager.h"
@@ -79,7 +80,8 @@
 }
 
 bool OsIntegrationManager::AreOsHooksSuppressedForTesting() {
-  return !GetSuppressCount().IsZero();
+  // If the override is present, os integration is turned on as it is handled.
+  return !GetSuppressCount().IsZero() && !OsIntegrationTestOverride::Get();
 }
 
 OsIntegrationManager::OsIntegrationManager(
diff --git a/chrome/browser/web_applications/preinstalled_web_app_manager.cc b/chrome/browser/web_applications/preinstalled_web_app_manager.cc
index 451f72a..851788a 100644
--- a/chrome/browser/web_applications/preinstalled_web_app_manager.cc
+++ b/chrome/browser/web_applications/preinstalled_web_app_manager.cc
@@ -33,6 +33,7 @@
 #include "base/threading/scoped_blocking_call.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "chrome/browser/web_applications/callback_utils.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 // TODO(crbug.com/1402145): Remove or at least isolate circular dependencies on
 // app service by moving this code to //c/b/web_applications/adjustments, or
@@ -73,6 +74,8 @@
 #include "url/gurl.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
+// TODO(http://b/333583704): Revert CL which added this include after migration.
+#include "chrome/browser/chromeos/echo/echo_util.h"
 #include "chrome/browser/web_applications/preinstalled_web_app_window_experiment_utils.h"
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
@@ -752,10 +755,42 @@
     return;
   }
 
-  LoadConfigs(base::BindOnce(
-      &PreinstalledWebAppManager::ParseConfigs, weak_ptr_factory_.GetWeakPtr(),
-      base::BindOnce(&PreinstalledWebAppManager::PostProcessConfigs,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
+  auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
+  RunChainedCallbacks(
+      base::BindOnce(&PreinstalledWebAppManager::LoadDeviceInfo, weak_ptr),
+      base::BindOnce(&PreinstalledWebAppManager::CacheDeviceInfo, weak_ptr),
+      base::BindOnce(&PreinstalledWebAppManager::LoadConfigs, weak_ptr),
+      base::BindOnce(&PreinstalledWebAppManager::ParseConfigs, weak_ptr),
+      base::BindOnce(&PreinstalledWebAppManager::PostProcessConfigs, weak_ptr),
+      std::move(callback));
+}
+
+// TODO(http://b/333583704): Revert CL which added this method after migration.
+void PreinstalledWebAppManager::LoadDeviceInfo(ConsumeDeviceInfo callback) {
+#if BUILDFLAG(IS_CHROMEOS)
+  chromeos::echo_util::GetOobeTimestamp(base::BindOnce(
+      [](ConsumeDeviceInfo callback,
+         base::expected<std::string, std::string> oobe_timestamp_or_error) {
+        DeviceInfo device_info;
+        if (oobe_timestamp_or_error.has_value() &&
+            oobe_timestamp_or_error.value().length()) {
+          device_info.oobe_timestamp =
+              std::move(oobe_timestamp_or_error.value());
+        }
+        std::move(callback).Run(std::move(device_info));
+      },
+      std::move(callback)));
+#else  // BUILDFLAG(IS_CHROMEOS)
+  std::move(callback).Run(DeviceInfo());
+#endif
+}
+
+// TODO(http://b/333583704): Revert CL which added this method after migration.
+void PreinstalledWebAppManager::CacheDeviceInfo(
+    CacheDeviceInfoCallback callback,
+    DeviceInfo device_info) {
+  device_info_ = std::move(device_info);
+  std::move(callback).Run();
 }
 
 void PreinstalledWebAppManager::LoadConfigs(ConsumeLoadedConfigs callback) {
@@ -814,7 +849,8 @@
     ConsumeInstallOptions callback,
     ParsedConfigs parsed_configs) {
   // Add hard coded configs.
-  for (ExternalInstallOptions& options : GetPreinstalledWebApps(*profile_)) {
+  for (ExternalInstallOptions& options :
+       GetPreinstalledWebApps(*profile_, device_info_)) {
     parsed_configs.options_list.push_back(std::move(options));
   }
 
diff --git a/chrome/browser/web_applications/preinstalled_web_app_manager.h b/chrome/browser/web_applications/preinstalled_web_app_manager.h
index daab317..3285777 100644
--- a/chrome/browser/web_applications/preinstalled_web_app_manager.h
+++ b/chrome/browser/web_applications/preinstalled_web_app_manager.h
@@ -7,6 +7,7 @@
 
 #include <map>
 #include <memory>
+#include <optional>
 #include <set>
 #include <vector>
 
@@ -19,6 +20,7 @@
 #include "chrome/browser/web_applications/external_install_options.h"
 #include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/file_utils_wrapper.h"
+#include "chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.h"
 #include "url/gurl.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
@@ -46,6 +48,8 @@
 // similar to WebAppPolicyManager.
 class PreinstalledWebAppManager {
  public:
+  using CacheDeviceInfoCallback = base::OnceClosure;
+  using ConsumeDeviceInfo = base::OnceCallback<void(DeviceInfo)>;
   using ConsumeLoadedConfigs = base::OnceCallback<void(LoadedConfigs)>;
   using ConsumeParsedConfigs = base::OnceCallback<void(ParsedConfigs)>;
   using ConsumeInstallOptions =
@@ -140,6 +144,9 @@
   void LoadAndSynchronize(SynchronizeCallback callback);
 
   void Load(ConsumeInstallOptions callback);
+  void LoadDeviceInfo(ConsumeDeviceInfo callback);
+  void CacheDeviceInfo(CacheDeviceInfoCallback callback,
+                       DeviceInfo device_info);
   void LoadConfigs(ConsumeLoadedConfigs callback);
   void ParseConfigs(ConsumeParsedConfigs callback,
                     LoadedConfigs loaded_configs);
@@ -182,6 +189,9 @@
 
   std::unique_ptr<DeviceDataInitializedEvent> device_data_initialized_event_;
 
+  // TODO(http://b/333583704): Revert CL which added this field after migration.
+  std::optional<DeviceInfo> device_info_;
+
   base::ObserverList<PreinstalledWebAppManager::Observer, /*check_empty=*/true>
       observers_;
 
diff --git a/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.cc b/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.cc
index a649820e6..c2d45d9 100644
--- a/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.cc
+++ b/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.cc
@@ -59,7 +59,9 @@
 }
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
-std::vector<ExternalInstallOptions> GetChromeBrandedApps(Profile& profile) {
+std::vector<ExternalInstallOptions> GetChromeBrandedApps(
+    Profile& profile,
+    const std::optional<DeviceInfo>& device_info) {
   bool is_standalone_tabbed =
       IsPreinstalledDocsSheetsSlidesDriveStandaloneTabbed(profile);
   // TODO(crbug.com/1104692): Replace these C++ configs with JSON configs like
@@ -82,7 +84,7 @@
 #if BUILDFLAG(IS_CHROMEOS)
       GetConfigForAppMall(),
       GetConfigForCalculator(),
-      GetConfigForContainer(),
+      GetConfigForContainer(), // TODO(http://b/331212317): Pass `device_info`.
       GetConfigForGoogleCalendar(),
       GetConfigForGoogleChat(),
       GetConfigForGoogleMeet(),
@@ -94,12 +96,26 @@
 
 }  // namespace
 
+DeviceInfo::DeviceInfo() = default;
+
+DeviceInfo::DeviceInfo(const DeviceInfo&) = default;
+
+DeviceInfo::DeviceInfo(DeviceInfo&&) = default;
+
+DeviceInfo& DeviceInfo::operator=(const DeviceInfo&) = default;
+
+DeviceInfo& DeviceInfo::operator=(DeviceInfo&&) = default;
+
+DeviceInfo::~DeviceInfo() = default;
+
 bool PreinstalledWebAppsDisabled() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
       ::switches::kDisableDefaultApps);
 }
 
-std::vector<ExternalInstallOptions> GetPreinstalledWebApps(Profile& profile) {
+std::vector<ExternalInstallOptions> GetPreinstalledWebApps(
+    Profile& profile,
+    const std::optional<DeviceInfo>& device_info) {
   if (g_preinstalled_app_data_for_testing)
     return *g_preinstalled_app_data_for_testing;
 
@@ -110,13 +126,14 @@
 #if BUILDFLAG(IS_CHROMEOS)
   // TODO(crbug/1346167): replace with config in admin console.
   if (IsGoogleInternalAccount()) {
-    std::vector<ExternalInstallOptions> apps = GetChromeBrandedApps(profile);
+    std::vector<ExternalInstallOptions> apps =
+        GetChromeBrandedApps(profile, device_info);
     apps.push_back(GetConfigForMessagesDogfood());
     return apps;
   }
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
-  return GetChromeBrandedApps(profile);
+  return GetChromeBrandedApps(profile, device_info);
 #else
   return {};
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
diff --git a/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.h b/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.h
index 7559dee..179dfc3 100644
--- a/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.h
+++ b/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_WEB_APPLICATIONS_PREINSTALLED_WEB_APPS_PREINSTALLED_WEB_APPS_H_
 #define CHROME_BROWSER_WEB_APPLICATIONS_PREINSTALLED_WEB_APPS_PREINSTALLED_WEB_APPS_H_
 
+#include <optional>
+#include <string>
 #include <vector>
 
 #include "chrome/browser/web_applications/external_install_options.h"
@@ -15,10 +17,30 @@
 
 namespace web_app {
 
+// TODO(http://b/333583704): Revert CL which added this struct after migration.
+struct DeviceInfo {
+  DeviceInfo();
+  DeviceInfo(const DeviceInfo&);
+  DeviceInfo(DeviceInfo&&);
+  DeviceInfo& operator=(const DeviceInfo&);
+  DeviceInfo& operator=(DeviceInfo&&);
+  ~DeviceInfo();
+
+#if BUILDFLAG(IS_CHROMEOS)
+  // The OOBE timestamp corresponding to the time of device registration. If
+  // present, the timestamp is a "y-M-d" formatted GMT date string. If absent,
+  // the timestamp is unavailable. This is known to occur during first boot due
+  // to a race condition between device registration and preinstallation.
+  std::optional<std::string> oobe_timestamp;
+#endif  // BUILDFLAG(IS_CHROMEOS)
+};
+
 bool PreinstalledWebAppsDisabled();
 
 // Returns the list of web apps that should be pre-installed on new profiles.
-std::vector<ExternalInstallOptions> GetPreinstalledWebApps(Profile& profile);
+std::vector<ExternalInstallOptions> GetPreinstalledWebApps(
+    Profile& profile,
+    const std::optional<DeviceInfo>& device_info = std::nullopt);
 
 // A subset of ExternalInstallOptions pertaining to web app migration.
 struct PreinstalledWebAppMigration {
diff --git a/chrome/browser/web_applications/web_app_registrar_unittest.cc b/chrome/browser/web_applications/web_app_registrar_unittest.cc
index 8d1468b..85e2a734 100644
--- a/chrome/browser/web_applications/web_app_registrar_unittest.cc
+++ b/chrome/browser/web_applications/web_app_registrar_unittest.cc
@@ -307,8 +307,8 @@
 }
 
 TEST_F(WebAppRegistrarTest, InitRegistrarAndDoForEachApp) {
-  base::flat_set<webapps::AppId> ids = PopulateRegistry(
-      CreateRegistryForTesting("https://example.com/path", 20));
+  base::flat_set<webapps::AppId> ids =
+      PopulateRegistry(CreateRegistryForTesting("https://example.com/path", 5));
   StartWebAppProvider();
 
   for (const WebApp& web_app : registrar().GetAppsIncludingStubs()) {
@@ -320,9 +320,9 @@
 }
 
 TEST_F(WebAppRegistrarTest, DoForEachAndUnregisterAllApps) {
-  Registry registry = CreateRegistryForTesting("https://example.com/path", 20);
+  Registry registry = CreateRegistryForTesting("https://example.com/path", 5);
   auto ids = PopulateRegistry(std::move(registry));
-  EXPECT_EQ(20UL, ids.size());
+  EXPECT_EQ(5UL, ids.size());
 
   StartWebAppProvider();
 
@@ -341,11 +341,11 @@
   base::HistogramTester histogram_tester;
 
   // All of these apps are marked as 'not locally installed'.
-  PopulateRegistry(CreateRegistryForTesting("https://example.com/path", 10));
+  PopulateRegistry(CreateRegistryForTesting("https://example.com/path", 5));
   StartWebAppProvider();
 
   histogram_tester.ExpectUniqueSample("WebApp.InstalledCount.ByUser",
-                                      /*sample=*/10,
+                                      /*sample=*/5,
                                       /*expected_bucket_count=*/1);
   histogram_tester.ExpectUniqueSample(
       "WebApp.InstalledCount.ByUserNotLocallyInstalled", /*sample=*/0,
@@ -394,7 +394,7 @@
 
 TEST_F(WebAppRegistrarTest, GetApps) {
   base::flat_set<webapps::AppId> ids =
-      PopulateRegistryWithApps("https://example.com/path", 10);
+      PopulateRegistryWithApps("https://example.com/path", 5);
   StartWebAppProvider();
 
   int not_in_sync_install_count = 0;
@@ -402,7 +402,7 @@
     ++not_in_sync_install_count;
     EXPECT_TRUE(base::Contains(ids, web_app.app_id()));
   }
-  EXPECT_EQ(10, not_in_sync_install_count);
+  EXPECT_EQ(5, not_in_sync_install_count);
 
   auto web_app_in_sync1 = test::CreateWebApp(GURL("https://example.org/sync1"));
   web_app_in_sync1->SetIsFromSyncAndPendingInstallation(true);
@@ -419,7 +419,7 @@
        registrar().GetAppsIncludingStubs()) {
     ++all_apps_count;
   }
-  EXPECT_EQ(12, all_apps_count);
+  EXPECT_EQ(7, all_apps_count);
 
   for (const WebApp& web_app : registrar().GetApps()) {
     EXPECT_NE(web_app_id_in_sync1, web_app.app_id());
@@ -437,7 +437,7 @@
   for ([[maybe_unused]] const WebApp& web_app : registrar().GetApps()) {
     ++not_in_sync_install_count;
   }
-  EXPECT_EQ(10, not_in_sync_install_count);
+  EXPECT_EQ(5, not_in_sync_install_count);
 }
 
 TEST_F(WebAppRegistrarTest, GetAppDataFields) {
@@ -751,7 +751,7 @@
 
 TEST_F(WebAppRegistrarTest, BeginAndCommitUpdate) {
   base::flat_set<webapps::AppId> ids =
-      PopulateRegistryWithApps("https://example.com/path", 10);
+      PopulateRegistryWithApps("https://example.com/path", 5);
   StartWebAppProvider();
 
   base::test::TestFuture<bool> future;
@@ -788,7 +788,7 @@
 
 TEST_F(WebAppRegistrarTest, CommitEmptyUpdate) {
   base::flat_set<webapps::AppId> ids =
-      PopulateRegistryWithApps("https://example.com/path", 10);
+      PopulateRegistryWithApps("https://example.com/path", 5);
   StartWebAppProvider();
   const auto initial_registry = database_factory().ReadRegistry();
 
@@ -822,7 +822,7 @@
 
 TEST_F(WebAppRegistrarTest, ScopedRegistryUpdate) {
   base::flat_set<webapps::AppId> ids =
-      PopulateRegistryWithApps("https://example.com/path", 10);
+      PopulateRegistryWithApps("https://example.com/path", 5);
   StartWebAppProvider();
   const auto initial_registry = database_factory().ReadRegistry();
 
@@ -1037,10 +1037,10 @@
 
 TEST_F(WebAppRegistrarTest,
        AppsFromSyncAndPendingInstallationExcludedFromGetAppIds) {
-  PopulateRegistryWithApps("https://example.com/path/", 20);
+  PopulateRegistryWithApps("https://example.com/path/", 5);
   StartWebAppProvider();
 
-  EXPECT_EQ(20u, registrar().GetAppIds().size());
+  EXPECT_EQ(5u, registrar().GetAppIds().size());
 
   std::unique_ptr<WebApp> web_app_in_sync_install =
       test::CreateWebApp(GURL("https://example.org/"));
@@ -1052,7 +1052,7 @@
 
   // Tests that GetAppIds() excludes web app in sync install:
   std::vector<webapps::AppId> ids = registrar().GetAppIds();
-  EXPECT_EQ(20u, ids.size());
+  EXPECT_EQ(5u, ids.size());
   for (const webapps::AppId& app_id : ids) {
     EXPECT_NE(app_id, web_app_in_sync_install_id);
   }
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index cd91f96..9d47602 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -247,10 +247,10 @@
 
 std::u16string GetMechanismDescription(
     device::AuthenticatorType type,
-    const std::optional<std::u16string>& priority_phone_name) {
+    const std::optional<std::string>& priority_phone_name) {
   if (type == device::AuthenticatorType::kPhone) {
     return l10n_util::GetStringFUTF16(IDS_WEBAUTHN_SOURCE_PHONE,
-                                      *priority_phone_name);
+                                      base::UTF8ToUTF16(*priority_phone_name));
   }
   int message;
   switch (type) {
@@ -398,14 +398,12 @@
     case AuthenticatorRequestDialogModel::Step::kClosed:
     case AuthenticatorRequestDialogModel::Step::kNotStarted:
     case AuthenticatorRequestDialogModel::Step::kConditionalMediation:
-    case AuthenticatorRequestDialogModel::Step::kWaitingForEnclave:
       return StepUIType::NONE;
 
     case AuthenticatorRequestDialogModel::Step::kRecoverSecurityDomain:
     case AuthenticatorRequestDialogModel::Step::kGPMReauthAccount:
       return StepUIType::WINDOW;
 
-    case AuthenticatorRequestDialogModel::Step::kGPMTouchID:
     case AuthenticatorRequestDialogModel::Step::kGPMPasskeySaved:
       return StepUIType::BUBBLE;
 
@@ -499,14 +497,6 @@
   return step_ui_type(step_) != StepUIType::BUBBLE;
 }
 
-std::optional<std::u16string>
-AuthenticatorRequestDialogModel::GetPriorityPhoneName() const {
-  if (!priority_phone_index) {
-    return std::nullopt;
-  }
-  return base::UTF8ToUTF16(paired_phone_names.at(*priority_phone_index));
-}
-
 #define AUTHENTICATOR_REQUEST_EVENT_0(name)      \
   void AuthenticatorRequestDialogModel::name() { \
     for (auto& observer : observers) {           \
@@ -1352,57 +1342,6 @@
   std::move(attestation_callback_).Run(attestation_permission_granted);
 }
 
-void AuthenticatorRequestDialogController::OnGPMOnboardingAccepted() {
-  DCHECK_EQ(model_->step(), Step::kGPMOnboarding);
-  SetCurrentStep(Step::kGPMCreatePin);
-}
-
-void AuthenticatorRequestDialogController::OnGPMCreatePasskey() {
-  DCHECK_EQ(model_->step(), Step::kGPMCreatePasskey);
-  DCHECK(account_state_ == AccountState::kReady ||
-         account_state_ == AccountState::kReadyWithPIN);
-  if (account_state_ == AccountState::kReady) {
-    SetCurrentStep(Step::kWaitingForEnclave);
-  } else if (account_state_ == AccountState::kReadyWithPIN) {
-    PromptForGPMPin();
-  }
-}
-
-void AuthenticatorRequestDialogController::OnTrustThisComputer() {
-  DCHECK_EQ(model_->step(), Step::kTrustThisComputer);
-  SetCurrentStep(Step::kRecoverSecurityDomain);
-}
-
-void AuthenticatorRequestDialogController::OnCreateGPMPin() {
-  SetCurrentStep(Step::kGPMCreatePin);
-}
-
-void AuthenticatorRequestDialogController::OnGPMPinOptionChanged(
-    bool is_arbitrary) {
-  DCHECK(model_->step() == Step::kGPMCreatePin ||
-         model_->step() == Step::kGPMCreateArbitraryPin);
-  SetCurrentStep(is_arbitrary ? Step::kGPMCreateArbitraryPin
-                              : Step::kGPMCreatePin);
-}
-
-std::string&& AuthenticatorRequestDialogController::TakeGPMPin() {
-  return std::move(gpm_pin_);
-}
-
-void AuthenticatorRequestDialogController::OnGPMPasskeySaved() {
-  SetCurrentStep(Step::kGPMPasskeySaved);
-}
-
-void AuthenticatorRequestDialogController::OnGPMPinEntered(
-    const std::u16string& pin) {
-  DCHECK(model_->step() == Step::kGPMCreateArbitraryPin ||
-         model_->step() == Step::kGPMCreatePin ||
-         model_->step() == Step::kGPMEnterArbitraryPin ||
-         model_->step() == Step::kGPMEnterPin);
-  gpm_pin_ = base::UTF16ToUTF8(pin);
-  SetCurrentStep(Step::kWaitingForEnclave);
-}
-
 void AuthenticatorRequestDialogController::AddAuthenticator(
     const device::FidoAuthenticator& authenticator) {
   // Only the webauthn.dll authenticator omits a transport completely. This
@@ -1475,6 +1414,7 @@
   DCHECK(account_preselected_callback_);
   account_preselected_callback_.Run(*cred);
   model_->creds.clear();
+  model_->preselected_cred = *cred;
 
   if (source != device::AuthenticatorType::kPhone &&
       source != device::AuthenticatorType::kEnclave) {
@@ -1485,62 +1425,7 @@
   if (!base::FeatureList::IsEnabled(device::kWebAuthnEnclaveAuthenticator)) {
     ContactPriorityPhone();
   } else {
-    switch (account_state_) {
-      case AccountState::kReady:
-        SetCurrentStep(Step::kWaitingForEnclave);
-        break;
-
-      case AccountState::kReadyWithPIN:
-        PromptForGPMPin();
-        break;
-
-      case AccountState::kReadyWithBiometrics:
-        SetCurrentStep(Step::kGPMTouchID);
-        break;
-
-      case AccountState::kRecoverable:
-        if (model_->priority_phone_index) {
-          SetCurrentStep(Step::kTrustThisComputer);
-        } else {
-          SetCurrentStep(Step::kRecoverSecurityDomain);
-        }
-        break;
-
-      case AccountState::kLoading:
-      case AccountState::kChecking:
-        // TODO(enclave): need to disable the UI elements.
-        NOTIMPLEMENTED();
-        break;
-
-      case AccountState::kNone:
-      case AccountState::kIrrecoverable:
-        if (model_->priority_phone_index) {
-          ContactPriorityPhone();
-        } else {
-          NOTIMPLEMENTED();
-        }
-        break;
-
-      case AccountState::kEmpty:
-        if (transport_availability_.request_type ==
-            device::FidoRequestType::kMakeCredential) {
-          if (model_->priority_phone_index) {
-            SetCurrentStep(Step::kTrustThisComputer);
-          } else {
-            SetCurrentStep(Step::kRecoverSecurityDomain);
-          }
-        } else {
-          if (model_->priority_phone_index) {
-            ContactPriorityPhone();
-          } else {
-            // TODO(enclave): the security domain is empty but there were
-            // sync entities. Most like the security domain was reset without
-            // clearing the entities, thus they are unusable. We have not yet
-            // decided what the behaviour will be in this case.
-            NOTIMPLEMENTED();
-          }
-        }
-    }
+    model_->OnGPMPasskeySelected(credential_id);
   }
 }
 
@@ -1558,7 +1443,7 @@
 }
 
 void AuthenticatorRequestDialogController::ContactPriorityPhone() {
-  ContactPhone(paired_phones_[*model_->priority_phone_index]->name);
+  ContactPhone(paired_phones_[*priority_phone_index_]->name);
 }
 
 void AuthenticatorRequestDialogController::ContactPhoneForTesting(
@@ -1569,6 +1454,16 @@
   ContactPhone(name);
 }
 
+void AuthenticatorRequestDialogController::SetPriorityPhoneIndex(
+    std::optional<size_t> index) {
+  if (index) {
+    model_->priority_phone_name = paired_phones_.at(*index)->name;
+  } else {
+    model_->priority_phone_name.reset();
+  }
+  priority_phone_index_ = index;
+}
+
 void AuthenticatorRequestDialogController::StartTransportFlowForTesting(
     AuthenticatorTransport transport) {
   StartGuidedFlowForTransport(transport);
@@ -1638,36 +1533,6 @@
                      : Step::kAttestationPermissionRequest);
 }
 
-AuthenticatorRequestDialogController::AccountState
-AuthenticatorRequestDialogController::account_state() const {
-  return account_state_;
-}
-
-void AuthenticatorRequestDialogController::set_account_state(
-    AccountState state) {
-  account_state_ = state;
-  if (model_->step() == Step::kRecoverSecurityDomain) {
-    if (state == AccountState::kReady) {
-      // The user completed the recovery that we were waiting for.
-      SetCurrentStep(Step::kWaitingForEnclave);
-    } else if (state == AccountState::kReadyWithPIN) {
-      // The account was recovered but now we need to prompt for an existing
-      // GPM PIN.
-      PromptForGPMPin();
-    }
-  }
-
-  if (waiting_for_account_state_to_start_enclave_) {
-    waiting_for_account_state_to_start_enclave_ = false;
-    StartEnclave();
-  }
-}
-
-void AuthenticatorRequestDialogController::set_gpm_pin_is_arbitrary(
-    bool is_arbitrary) {
-  gpm_pin_is_arbitrary_ = is_arbitrary;
-}
-
 void AuthenticatorRequestDialogController::set_cable_transport_info(
     std::optional<bool> extension_is_v2,
     std::vector<std::unique_ptr<device::cablev2::Pairing>> paired_phones,
@@ -1716,19 +1581,13 @@
   should_create_in_icloud_keychain_ = is_enabled;
 }
 
+void AuthenticatorRequestDialogController::set_enclave_enabled(
+    bool is_enabled) {
+  enclave_enabled_ = is_enabled;
+}
+
 #if BUILDFLAG(IS_MAC)
 
-void AuthenticatorRequestDialogController::OnTouchIDComplete(bool success) {
-  // On error no LAContext will be provided and macOS will show the system UI
-  // for user verification.
-  SetCurrentStep(Step::kWaitingForEnclave);
-}
-
-std::optional<crypto::ScopedLAContext>
-AuthenticatorRequestDialogController::TakeLAContext() {
-  return std::move(model_->lacontext);
-}
-
 // This enum is used in a histogram. Never change assigned values and only add
 // new entries at the end.
 enum class MacOsHistogramValues {
@@ -1953,39 +1812,7 @@
 }
 
 void AuthenticatorRequestDialogController::StartEnclave() {
-  switch (account_state_) {
-    case AccountState::kReady:
-    case AccountState::kReadyWithPIN:
-      SetCurrentStep(Step::kGPMCreatePasskey);
-      break;
-
-    case AccountState::kReadyWithBiometrics:
-      SetCurrentStep(Step::kGPMTouchID);
-      break;
-
-    case AccountState::kRecoverable:
-      SetCurrentStep(Step::kTrustThisComputer);
-      break;
-
-    case AccountState::kLoading:
-    case AccountState::kChecking:
-      waiting_for_account_state_to_start_enclave_ = true;
-      DisableUI();
-      break;
-
-    case AccountState::kNone:
-      NOTREACHED();
-      break;
-
-    case AccountState::kIrrecoverable:
-      // TODO(enclave): show the reset flow.
-      NOTIMPLEMENTED();
-      break;
-
-    case AccountState::kEmpty:
-      SetCurrentStep(Step::kGPMOnboarding);
-      break;
-  }
+  model_->OnGPMSelected();
 }
 
 void AuthenticatorRequestDialogController::ContactPhone(
@@ -2202,11 +2029,9 @@
 void AuthenticatorRequestDialogController::PopulateMechanisms() {
   const bool is_get_assertion = transport_availability_.request_type ==
                                 device::FidoRequestType::kGetAssertion;
-  model_->priority_phone_index = GetIndexOfMostRecentlyUsedPhoneFromSync();
-  std::optional<std::u16string> priority_phone_name =
-      model_->GetPriorityPhoneName();
+  SetPriorityPhoneIndex(GetIndexOfMostRecentlyUsedPhoneFromSync());
   bool list_phone_passkeys =
-      is_get_assertion && model_->priority_phone_index &&
+      is_get_assertion && priority_phone_index_ &&
       base::FeatureList::IsEnabled(syncer::kSyncWebauthnCredentials);
   bool specific_phones_listed = false;
   bool specific_local_passkeys_listed = false;
@@ -2235,7 +2060,7 @@
               &AuthenticatorRequestDialogController::OnAccountPreselected,
               base::Unretained(this), cred.cred_id));
       mechanism.description =
-          GetMechanismDescription(cred.source, priority_phone_name);
+          GetMechanismDescription(cred.source, model_->priority_phone_name);
     }
   }
 
@@ -2320,7 +2145,7 @@
   }
 
   if (base::FeatureList::IsEnabled(device::kWebAuthnEnclaveAuthenticator) &&
-      account_state_ != AccountState::kNone && !is_get_assertion) {
+      enclave_enabled_ && !is_get_assertion) {
     const std::u16string name = u"Google Password Manager (UNTRANSLATED)";
     model_->mechanisms.emplace_back(
         Mechanism::Enclave(), name, name, kIcloudKeychainIcon,
@@ -2377,7 +2202,7 @@
         paired_phones_.size() == 1 && !use_conditional_mediation_ &&
         transport_availability_.is_only_hybrid_or_internal;
     if (skip_to_phone_confirmation) {
-      model_->priority_phone_index = 0;
+      SetPriorityPhoneIndex(0);
       pending_step_ = Step::kPhoneConfirmationSheet;
     }
   }
@@ -2642,11 +2467,6 @@
   return std::nullopt;
 }
 
-void AuthenticatorRequestDialogController::PromptForGPMPin() {
-  SetCurrentStep(gpm_pin_is_arbitrary_ ? Step::kGPMEnterArbitraryPin
-                                       : Step::kGPMEnterPin);
-}
-
 void AuthenticatorRequestDialogController::OnPasskeysChanged(
     const std::vector<webauthn::PasskeyModelChange>& changes) {
   if (model_->step() != Step::kConditionalMediation) {
@@ -2689,11 +2509,6 @@
   model_->mechanisms[*model_->priority_mechanism_index].callback.Run();
 }
 
-void AuthenticatorRequestDialogController::DisableUI() {
-  model_->ui_disabled_ = true;
-  model_->OnSheetModelChanged();
-}
-
 void AuthenticatorRequestDialogController::
     HideDialogAndDispatchToPlatformAuthenticator(
         std::optional<device::AuthenticatorType> type) {
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.h b/chrome/browser/webauthn/authenticator_request_dialog_model.h
index d23fe45..1f33e51 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.h
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.h
@@ -106,7 +106,11 @@
   /* Called when the user cancelled WebAuthn request by clicking the */       \
   /* "cancel" button or the back arrow in the UI dialog. */                   \
   AUTHENTICATOR_REQUEST_EVENT_0(OnCancelRequest)                              \
+  /* Called when the user picks Google Password Manager from the */           \
+  /* mechanism selection sheet. */                                            \
+  AUTHENTICATOR_REQUEST_EVENT_0(OnGPMSelected)                                \
   /* Called when the user accepts the create passkey sheet. */                \
+  /* (But not the GPM one.) */                                                \
   AUTHENTICATOR_REQUEST_EVENT_0(OnCreatePasskeyAccepted)                      \
   /* Called when the user accepts passkey creation in the GPM bubble. */      \
   /* TODO(enclave): Add transition to authentication or bootstrapping  */     \
@@ -122,6 +126,9 @@
   /* the interstitial that warns that platform/caBLE authenticators may */    \
   /* record information even in incognito mode. */                            \
   AUTHENTICATOR_REQUEST_EVENT_0(OnOffTheRecordInterstitialAccepted)           \
+  /* Sent by GPMEnclaveController when it's ready for the UI to be */         \
+  /* displayed. */                                                            \
+  AUTHENTICATOR_REQUEST_EVENT_0(OnReadyForUI)                                 \
   /* Called when a user closes the MagicArch window. */                       \
   AUTHENTICATOR_REQUEST_EVENT_0(OnRecoverSecurityDomainClosed)                \
   /* To be called when the Web Authentication request is complete. */         \
@@ -159,6 +166,9 @@
   /* OnAttestationPermissionResponse is called when the user either */        \
   /* allows or disallows an attestation permission request. */                \
   AUTHENTICATOR_REQUEST_EVENT_1(OnAttestationPermissionResponse, bool)        \
+  /* Called when the user selects a GPM passkey. */                           \
+  AUTHENTICATOR_REQUEST_EVENT_1(OnGPMPasskeySelected,                         \
+                                base::span<const uint8_t>)                    \
   /* Called when the user enters the GPM pin in the UI (during initial */     \
   /* setup or authentication). */                                             \
   AUTHENTICATOR_REQUEST_EVENT_1(OnGPMPinEntered, const std::u16string&)       \
@@ -282,7 +292,7 @@
     kGPMCreateArbitraryPin,
     kGPMEnterArbitraryPin,
 
-    // User verification prompt for GPM.
+    // User verification prompt for GPM. Only valid on macOS 12+.
     kGPMTouchID,
 
     // GPM passkey creation.
@@ -294,7 +304,6 @@
     // Device bootstrap to use GPM passkeys.
     kRecoverSecurityDomain,
     kTrustThisComputer,
-    kWaitingForEnclave,
 
     // Changing GPM PIN.
     kGPMReauthAccount,
@@ -428,11 +437,6 @@
   // Similar to above, but for bubbles.
   bool should_bubble_be_closed() const;
 
-  // Returns the name of the "priority" paired phone. This is the phone from
-  // sync if there are a priori discovered GPM passkeys, or the first phone on
-  // the list otherwise.
-  std::optional<std::u16string> GetPriorityPhoneName() const;
-
   const std::optional<content::GlobalRenderFrameHostId> frame_host_id;
   device::FidoRequestType request_type = device::FidoRequestType::kGetAssertion;
   device::ResidentKeyRequirement resident_key_requirement =
@@ -448,6 +452,8 @@
   // creds contains possible credentials to select between before or after an
   // authenticator has responded to a request.
   std::vector<device::DiscoverableCredentialMetadata> creds;
+  // preselected_cred contains a credential preselected by the user.
+  std::optional<device::DiscoverableCredentialMetadata> preselected_cred;
   // offer_try_again_in_ui indicates whether a button to retry the request
   // should be included on the dialog sheet shown when encountering certain
   // errors.
@@ -464,9 +470,10 @@
   std::optional<int> pin_attempts;
   std::optional<int> uv_attempts;
   device::pin::PINEntryError pin_error = device::pin::PINEntryError::kNoError;
-  std::optional<size_t> priority_phone_index;
   // A sorted, unique list of the names of paired phones.
   std::vector<std::string> paired_phone_names;
+  // The name of the priority phone, if any.
+  std::optional<std::string> priority_phone_name;
 
   // cable_ui_type contains the type of UI to display for a caBLE transaction.
   std::optional<CableUIType> cable_ui_type;
@@ -512,29 +519,6 @@
   using TransportAvailabilityInfo =
       device::FidoRequestHandlerBase::TransportAvailabilityInfo;
 
-  enum class AccountState {
-    // There isn't a primary account, or enclave support is disabled.
-    kNone,
-    // The enclave state is still being loaded from disk.
-    kLoading,
-    // The state of the account is unknown pending network requests.
-    kChecking,
-    // The account can be recovered via user action.
-    kRecoverable,
-    // The account cannot be recovered, but could be reset.
-    kIrrecoverable,
-    // The security domain is empty.
-    kEmpty,
-    // The enclave is ready to use.
-    kReady,
-    // The enclave is ready to use, but the UI needs to collect a PIN before
-    // making a transaction.
-    kReadyWithPIN,
-    // The enclave is ready to use, but the UI needs to collect biometrics
-    // before making a transaction.
-    kReadyWithBiometrics,
-  };
-
   explicit AuthenticatorRequestDialogController(Model* model);
 
   AuthenticatorRequestDialogController(
@@ -749,21 +733,6 @@
   void OnResidentCredentialConfirmed() override;
   void OnAttestationPermissionResponse(
       bool attestation_permission_granted) override;
-  void OnGPMOnboardingAccepted() override;
-  void OnGPMCreatePasskey() override;
-  void OnGPMPinEntered(const std::u16string& pin) override;
-  void OnTrustThisComputer() override;
-
-  // Called when the user needs to set their GPM PIN for the first time.
-  void OnCreateGPMPin();
-
-  void OnGPMPinOptionChanged(bool is_arbitrary) override;
-
-  // Return the last entered GPM PIN.
-  std::string&& TakeGPMPin();
-
-  // Called when the passkey creation is successful.
-  void OnGPMPasskeySaved();
 
   // Adds or removes an authenticator to the list of known authenticators. The
   // first authenticator added with transport `kInternal` (or without a
@@ -794,6 +763,10 @@
   // user-visible mechanisms and use the callbacks therein.
   void ContactPhoneForTesting(const std::string& name);
 
+  // Sets `priority_phone_index_` and updates the name of the priority phone in
+  // `model_` accordingly.
+  void SetPriorityPhoneIndex(std::optional<size_t> index);
+
   // StartTransportFlowForTesting moves the UI to focus on the given transport.
   // UI should use |mechanisms()| to enumerate the user-visible mechanisms and
   // use the callbacks therein.
@@ -835,11 +808,6 @@
     is_non_webauthn_request_ = is_non_webauthn_request;
   }
 
-  AccountState account_state() const;
-  void set_account_state(AccountState);
-
-  void set_gpm_pin_is_arbitrary(bool is_arbitrary);
-
   void SetHints(
       const content::AuthenticatorRequestClientDelegate::Hints& hints) {
     hints_ = hints;
@@ -858,13 +826,9 @@
 
   void set_allow_icloud_keychain(bool);
   void set_should_create_in_icloud_keychain(bool);
+  void set_enclave_enabled(bool);
 
 #if BUILDFLAG(IS_MAC)
-  void OnTouchIDComplete(bool success) override;
-
-  // Returns the authenticated LAContext, if any.
-  std::optional<crypto::ScopedLAContext> TakeLAContext();
-
   void RecordMacOsStartedHistogram();
   void RecordMacOsSuccessHistogram(device::FidoRequestType,
                                    device::AuthenticatorType);
@@ -978,9 +942,6 @@
 
   void OnUserConfirmedPriorityMechanism() override;
 
-  // Disables the UI elements of the currently displayed sheet.
-  void DisableUI();
-
   raw_ptr<Model> model_;
 
   // Identifier for the RenderFrameHost of the frame that initiated the current
@@ -1037,6 +998,9 @@
   // QR-based pairing. The entries are sorted by name.
   std::vector<std::unique_ptr<device::cablev2::Pairing>> paired_phones_;
 
+  // The index, into `paired_phones_`, for the top-priority phone.
+  std::optional<size_t> priority_phone_index_;
+
   // paired_phones_contacted_ is the same length as |paired_phones_| and
   // contains true whenever the corresponding phone as already been contacted.
   std::vector<bool> paired_phones_contacted_;
@@ -1071,23 +1035,14 @@
   // profile authenticator.
   bool should_create_in_icloud_keychain_ = false;
 
+  // enclave_enabled_ is true if a "Google Password Manager" entry should be
+  // offered as a mechanism for creating a credential.
+  bool enclave_enabled_ = false;
+
   // The RP's hints. See
   // https://w3c.github.io/webauthn/#enumdef-publickeycredentialhints
   content::AuthenticatorRequestClientDelegate::Hints hints_;
 
-  // Records the state of the primary account for the profile, if any.
-  AccountState account_state_ = AccountState::kNone;
-
-  // If true then the GPM PIN is known to be an arbitrary string rather than
-  // the default 6-digit number.
-  bool gpm_pin_is_arbitrary_ = false;
-
-  // The entered GPM PIN.
-  std::string gpm_pin_;
-
-  // Whether the UI is currently disabled, waiting for account state to load.
-  bool waiting_for_account_state_to_start_enclave_ = false;
-
 #if BUILDFLAG(IS_MAC)
   // did_record_macos_start_histogram_ is set to true if a histogram record of
   // starting the current request was made. Any later successful completion will
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
index 6284f08..c91590f 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
@@ -1554,6 +1554,33 @@
   }
 }
 
+TEST_F(AuthenticatorRequestDialogControllerTest, CrBug333592767) {
+  AuthenticatorRequestDialogModel model(main_rfh());
+  AuthenticatorRequestDialogController controller(&model);
+
+  auto phone1 = std::make_unique<device::cablev2::Pairing>();
+  phone1->name = "test";
+  phone1->from_sync_deviceinfo = true;
+  phone1->last_updated = base::Time::FromTimeT(1);
+  auto phone2 = std::make_unique<device::cablev2::Pairing>(*phone1);
+  phone2->last_updated = base::Time::FromTimeT(2);
+
+  std::vector<std::unique_ptr<device::cablev2::Pairing>> phones;
+  phones.emplace_back(std::move(phone1));
+  phones.emplace_back(std::move(phone2));
+
+  controller.set_cable_transport_info(/*extension_is_v2=*/false,
+                                      std::move(phones), base::DoNothing(),
+                                      std::nullopt);
+  TransportAvailabilityInfo transports_info;
+  transports_info.request_type = RequestType::kMakeCredential;
+  transports_info.make_credential_attachment =
+      device::AuthenticatorAttachment::kAny;
+  transports_info.available_transports = kAllTransportsWithoutCable;
+  controller.StartFlow(std::move(transports_info),
+                       /*is_conditional_mediation=*/false);
+}
+
 TEST_F(AuthenticatorRequestDialogControllerTest, AwaitingAcknowledgement) {
   const struct {
     void (AuthenticatorRequestDialogController::*event)();
@@ -2206,7 +2233,7 @@
   controller.StartFlow(std::move(transports_info),
                        /*is_conditional_mediation=*/false);
   EXPECT_EQ(model.step(), Step::kPhoneConfirmationSheet);
-  EXPECT_EQ(model.GetPriorityPhoneName(), u"Phone from QR");
+  EXPECT_EQ(model.priority_phone_name, "Phone from QR");
   model.ContactPriorityPhone();
   EXPECT_EQ(model.step(), Step::kCableActivate);
   EXPECT_EQ(model.selected_phone_name, "Phone from QR");
@@ -2240,7 +2267,7 @@
   controller.StartFlow(std::move(transports_info),
                        /*is_conditional_mediation=*/false);
   EXPECT_EQ(model.step(), Step::kPhoneConfirmationSheet);
-  EXPECT_EQ(model.GetPriorityPhoneName(), u"Phone from sync");
+  EXPECT_EQ(model.priority_phone_name, "Phone from sync");
   model.ContactPriorityPhone();
   EXPECT_EQ(model.step(), Step::kCableActivate);
   EXPECT_EQ(model.selected_phone_name, "Phone from sync");
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
index b324669..1c55767 100644
--- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
+++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
@@ -20,7 +20,6 @@
 #include "base/memory/raw_ptr.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_split.h"
-#include "base/task/thread_pool.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -39,10 +38,8 @@
 #include "chrome/browser/ui/page_action/page_action_icon_type.h"
 #include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
 #include "chrome/browser/webauthn/cablev2_devices.h"
-#include "chrome/browser/webauthn/enclave_manager.h"
-#include "chrome/browser/webauthn/enclave_manager_factory.h"
+#include "chrome/browser/webauthn/gpm_enclave_controller.h"
 #include "chrome/browser/webauthn/passkey_model_factory.h"
-#include "chrome/browser/webauthn/proto/enclave_local_state.pb.h"
 #include "chrome/browser/webauthn/webauthn_pref_names.h"
 #include "chrome/browser/webauthn/webauthn_switches.h"
 #include "chrome/common/chrome_switches.h"
@@ -108,8 +105,6 @@
 #include "ui/aura/window.h"
 #endif
 
-using AccountState = AuthenticatorRequestDialogController::AccountState;
-
 namespace {
 
 ChromeAuthenticatorRequestDelegate::TestObserver* g_observer = nullptr;
@@ -296,54 +291,6 @@
   raw_ptr<Profile> profile_;
 };
 
-// EnclaveUserVerificationMethod enumerates the possible ways that user
-// verification will be performed for an enclave transaction.
-enum class EnclaveUserVerificationMethod {
-  // No user verification will be performed.
-  kNone,
-  // The user will enter a GPM PIN.
-  kPIN,
-  // The operating system will perform user verification and allow signing
-  // with the UV key.
-  kUVKeyWithSystemUI,
-  // Chrome will show user verification UI for the operating system, which will
-  // then allow signing
-  // with the UV key.
-  kUVKeyWithChromeUI,
-  // The request cannot be satisfied.
-  kUnsatisfiable,
-};
-
-// Pick an enclave user verification method for a specific request.
-EnclaveUserVerificationMethod PickEnclaveUserVerificationMethod(
-    device::UserVerificationRequirement uv,
-    bool has_pin,
-    EnclaveManager::UvKeyState uv_key_state) {
-  switch (uv) {
-    case device::UserVerificationRequirement::kDiscouraged:
-      return EnclaveUserVerificationMethod::kNone;
-
-    case device::UserVerificationRequirement::kPreferred:
-    case device::UserVerificationRequirement::kRequired:
-      switch (uv_key_state) {
-        case EnclaveManager::UvKeyState::kNone:
-          if (has_pin) {
-            return EnclaveUserVerificationMethod::kPIN;
-          } else if (uv == device::UserVerificationRequirement::kPreferred) {
-            return EnclaveUserVerificationMethod::kNone;
-          } else {
-            return EnclaveUserVerificationMethod::kUnsatisfiable;
-          }
-
-        case EnclaveManager::UvKeyState::kUsesSystemUI:
-          return EnclaveUserVerificationMethod::kUVKeyWithSystemUI;
-
-        case EnclaveManager::UvKeyState::kUsesChromeUI:
-          return EnclaveUserVerificationMethod::kUVKeyWithChromeUI;
-      }
-  }
-}
-
 }  // namespace
 
 // ---------------------------------------------------------------------
@@ -646,6 +593,11 @@
   return weak_ptr_factory_.GetWeakPtr();
 }
 
+GPMEnclaveController*
+ChromeAuthenticatorRequestDelegate::enclave_controller_for_testing() const {
+  return enclave_controller_.get();
+}
+
 void ChromeAuthenticatorRequestDelegate::SetRelyingPartyId(
     const std::string& rp_id) {
   dialog_model_->relying_party_id = rp_id;
@@ -749,9 +701,8 @@
   account_preselected_callback_ = std::move(account_preselected_callback);
 
   dialog_controller_->SetRequestCallback(request_callback);
-  dialog_controller_->SetAccountPreselectedCallback(base::BindRepeating(
-      &ChromeAuthenticatorRequestDelegate::OnAccountPreselected,
-      weak_ptr_factory_.GetWeakPtr()));
+  dialog_controller_->SetAccountPreselectedCallback(
+      account_preselected_callback_);
   dialog_controller_->SetBluetoothAdapterPowerOnCallback(
       bluetooth_adapter_power_on_callback);
 }
@@ -822,32 +773,14 @@
     auto* const identity_manager =
         IdentityManagerFactory::GetForProfile(profile);
     if (identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
-      webauthn::PasskeyModel* passkey_model =
-          PasskeyModelFactory::GetInstance()->GetForProfile(profile);
-      gpm_credentials_ = passkey_model->GetPasskeysForRelyingPartyId(rp_id);
-
-      enclave_manager_ = EnclaveManagerFactory::GetForProfile(profile);
-      enclave_manager_observer_ =
-          std::make_unique<EnclaveManagerObserver>(this, enclave_manager_);
-      if (gpm_credentials_->empty() &&
-          request_type == device::FidoRequestType::kGetAssertion) {
-        // No possibility of using GPM for this request.
-        FIDO_LOG(EVENT) << "Enclave is not a candidate for this request";
-        dialog_controller_->set_account_state(AccountState::kNone);
-      } else if (enclave_manager_->is_ready()) {
-        FIDO_LOG(EVENT) << "Enclave is ready";
-        SetAccountStateReady();
-      } else if (enclave_manager_->is_loaded()) {
-        FIDO_LOG(EVENT) << "Account state needs to be checked";
-        dialog_controller_->set_account_state(AccountState::kChecking);
-        DownloadAccountState();
-      } else {
-        FIDO_LOG(EVENT) << "Enclave state is loading";
-        enclave_manager_->Load(
-            base::BindOnce(&ChromeAuthenticatorRequestDelegate::OnEnclaveLoaded,
-                           weak_ptr_factory_.GetWeakPtr()));
-        dialog_controller_->set_account_state(AccountState::kLoading);
+      enclave_controller_ = std::make_unique<GPMEnclaveController>(
+          GetRenderFrameHost(), dialog_model_.get(), rp_id, request_type,
+          user_verification_requirement);
+      if (pending_trusted_vault_connection_) {
+        enclave_controller_->SetTrustedVaultConnectionForTesting(
+            std::move(pending_trusted_vault_connection_));
       }
+      dialog_controller_->set_enclave_enabled(true);
     }
   }
 
@@ -962,7 +895,7 @@
   }
 
   if (non_extension_cablev2_enabled || cablev2_extension_provided ||
-      enclave_manager_) {
+      enclave_controller_) {
     if (SystemNetworkContextManager::GetInstance()) {
       // TODO(nsatragno): this should probably use a storage partition network
       // context instead. See the SystemNetworkContextManager class comments.
@@ -1012,8 +945,8 @@
   }
 #endif
 
-  if (enclave_manager_) {
-    ConfigureEnclaveDiscovery(rp_id, discovery_factory);
+  if (enclave_controller_) {
+    enclave_controller_->ConfigureDiscoveries(discovery_factory);
   }
 
   dialog_controller_->set_is_non_webauthn_request(
@@ -1105,7 +1038,7 @@
     }
   }
   if (base::FeatureList::IsEnabled(syncer::kSyncWebauthnCredentials) &&
-      (can_use_synced_phone_passkeys_ || enclave_manager_) &&
+      (can_use_synced_phone_passkeys_ || enclave_controller_) &&
       !IsVirtualEnvironmentEnabled()) {
     GetPhoneContactableGpmPasskeysForRpId(&data.recognized_credentials);
   }
@@ -1136,9 +1069,10 @@
     return;
   }
 
-  if (enclave_manager_ && !enclave_manager_->is_loaded()) {
+  if (enclave_controller_ && !enclave_controller_->ready_for_ui()) {
     // Delay showing UI until the enclave state is loaded. This avoids some
     // complexity later that would other come from dealing with this case.
+    // Showing the UI is handled in `OnReadyForUI`.
     pending_transport_availability_info_ = std::make_unique<
         device::FidoRequestHandlerBase::TransportAvailabilityInfo>(
         std::move(data));
@@ -1239,49 +1173,6 @@
   DCHECK_EQ(model, dialog_model_.get());
 }
 
-void ChromeAuthenticatorRequestDelegate::OnStepTransition() {
-  bool start_transaction = false;
-
-  if (dialog_model_->step() ==
-      AuthenticatorRequestDialogModel::Step::kWaitingForEnclave) {
-    if (dialog_controller_->account_state() == AccountState::kRecoverable &&
-        enclave_manager_->has_pending_keys()) {
-      // In this case, we were waiting for the user to create their GPM PIN
-      // and the needed enclave action is to set up using that PIN.
-      gpm_pin_stashed_ = dialog_controller_->TakeGPMPin();
-      enclave_manager_->AddDeviceAndPINToAccount(
-          *gpm_pin_stashed_,
-          base::BindOnce(&ChromeAuthenticatorRequestDelegate::OnDeviceAdded,
-                         weak_ptr_factory_.GetWeakPtr()));
-      return;
-    }
-
-    if (dialog_controller_->account_state() == AccountState::kEmpty) {
-      // The user has set a PIN to create the account.
-      gpm_pin_stashed_ = dialog_controller_->TakeGPMPin();
-      enclave_manager_->SetupWithPIN(
-          *gpm_pin_stashed_,
-          base::BindOnce(&ChromeAuthenticatorRequestDelegate::OnDeviceAdded,
-                         weak_ptr_factory_.GetWeakPtr()));
-      return;
-    }
-
-    start_transaction = true;
-  } else if (dialog_model_->step() == AuthenticatorRequestDialogModel::Step::
-                                          kRecoverSecurityDomain &&
-             enclave_manager_->is_ready()) {
-    // Finished setting up without enrolling a GPM PIN.
-    start_transaction = true;
-  }
-
-  if (start_transaction) {
-    access_token_fetcher_ = enclave_manager_->GetAccessToken(
-        base::BindOnce(&ChromeAuthenticatorRequestDelegate::
-                           MaybeHashPinAndStartEnclaveTransaction,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-}
-
 void ChromeAuthenticatorRequestDelegate::OnCancelRequest() {
   // |cancel_callback_| must be invoked at most once as invocation of
   // |cancel_callback_| will destroy |this|.
@@ -1309,31 +1200,14 @@
 
 void ChromeAuthenticatorRequestDelegate::SetTrustedVaultConnectionForTesting(
     std::unique_ptr<trusted_vault::TrustedVaultConnection> connection) {
-  vault_connection_override_ = std::move(connection);
+  if (enclave_controller_) {
+    enclave_controller_->SetTrustedVaultConnectionForTesting(
+        std::move(connection));
+  } else {
+    pending_trusted_vault_connection_ = std::move(connection);
+  }
 }
 
-class ChromeAuthenticatorRequestDelegate::EnclaveManagerObserver
-    : public EnclaveManager::Observer {
- public:
-  EnclaveManagerObserver(ChromeAuthenticatorRequestDelegate* delegate,
-                         EnclaveManager* manager)
-      : delegate_(delegate), manager_(manager) {
-    manager_->AddObserver(this);
-  }
-
-  ~EnclaveManagerObserver() override { manager_->RemoveObserver(this); }
-
-  // EnclaveManager::Observer:
-  void OnKeysStored() override {
-    // `delegate_` owns this object and so `delegate_` must be valid.
-    delegate_->OnKeysStored();
-  }
-
- private:
-  const raw_ptr<ChromeAuthenticatorRequestDelegate> delegate_;
-  const raw_ptr<EnclaveManager> manager_;
-};
-
 content::RenderFrameHost*
 ChromeAuthenticatorRequestDelegate::GetRenderFrameHost() const {
   content::RenderFrameHost* ret =
@@ -1356,280 +1230,16 @@
   }
 }
 
-void ChromeAuthenticatorRequestDelegate::OnEnclaveLoaded() {
-  CHECK_EQ(dialog_controller_->account_state(), AccountState::kLoading);
-
-  if (enclave_manager_->is_ready()) {
-    FIDO_LOG(EVENT) << "Enclave is ready";
-    SetAccountStateReady();
-  } else {
-    FIDO_LOG(EVENT) << "Account state needs to be checked";
-    dialog_controller_->set_account_state(AccountState::kChecking);
-    DownloadAccountState();
-  }
-
-  if (pending_transport_availability_info_) {
-    auto pending_transport_availability_info =
-        std::move(pending_transport_availability_info_);
-    pending_transport_availability_info_.reset();
-
-    ShowUI(std::move(*pending_transport_availability_info));
-  }
-}
-
-void ChromeAuthenticatorRequestDelegate::OnKeysStored() {
-  if (dialog_model_->step() !=
-      AuthenticatorRequestDialogModel::Step::kRecoverSecurityDomain) {
-    return;
-  }
-  CHECK(enclave_manager_->has_pending_keys());
-  CHECK(!enclave_manager_->is_ready());
-
-  if (pin_metadata_.has_value()) {
-    // The account already has a GPM PIN.
-    if (!enclave_manager_->AddDeviceToAccount(
-            std::move(pin_metadata_),
-            base::BindOnce(&ChromeAuthenticatorRequestDelegate::OnDeviceAdded,
-                           weak_ptr_factory_.GetWeakPtr()))) {
-      // TODO(enclave): move the UI to a to-be-created error state.
-      NOTREACHED();
-    }
-  } else {
-    // If the user has local biometrics, and an existing recovery factor,
-    // we'll likely choose not to create a GPM PIN. For now, however, we
-    // always do:
-    dialog_controller_->OnCreateGPMPin();
-  }
-}
-
-void ChromeAuthenticatorRequestDelegate::OnDeviceAdded(bool success) {
-  if (!success) {
-    // TODO(enclave): move the UI to a to-be-created error state.
-    NOTREACHED();
+void ChromeAuthenticatorRequestDelegate::OnReadyForUI() {
+  if (!pending_transport_availability_info_) {
     return;
   }
 
-  SetAccountStateReady();
-  // Trigger the authenticator request to the enclave.
-  OnStepTransition();
-}
+  auto pending_transport_availability_info =
+      std::move(pending_transport_availability_info_);
+  pending_transport_availability_info_.reset();
 
-void ChromeAuthenticatorRequestDelegate::OnAccountPreselected(
-    device::DiscoverableCredentialMetadata descriptor) {
-  preselected_cred_id_ = descriptor.cred_id;
-  account_preselected_callback_.Run(std::move(descriptor));
-}
-
-void ChromeAuthenticatorRequestDelegate::DownloadAccountState() {
-  auto* const identity_manager = IdentityManagerFactory::GetForProfile(
-      Profile::FromBrowserContext(GetBrowserContext()));
-  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
-      SystemNetworkContextManager::GetInstance()->GetSharedURLLoaderFactory();
-  std::unique_ptr<trusted_vault::TrustedVaultConnection> trusted_vault_conn =
-      vault_connection_override_
-          ? std::move(vault_connection_override_)
-          : trusted_vault::NewFrontendTrustedVaultConnection(
-                trusted_vault::SecurityDomainId::kPasskeys, identity_manager,
-                url_loader_factory);
-  download_account_state_request_ =
-      trusted_vault_conn->DownloadAuthenticationFactorsRegistrationState(
-          identity_manager->GetPrimaryAccountInfo(
-              signin::ConsentLevel::kSignin),
-          base::BindOnce(
-              &ChromeAuthenticatorRequestDelegate::OnAccountStateDownloaded,
-              weak_ptr_factory_.GetWeakPtr(), std::move(trusted_vault_conn)));
-}
-
-void ChromeAuthenticatorRequestDelegate::SetAccountStateReady() {
-  const bool has_pin = enclave_manager_->has_wrapped_pin();
-
-  AccountState account_state;
-  switch (PickEnclaveUserVerificationMethod(*user_verification_requirement_,
-                                            enclave_manager_->has_wrapped_pin(),
-                                            enclave_manager_->uv_key_state())) {
-    case EnclaveUserVerificationMethod::kUVKeyWithSystemUI:
-    case EnclaveUserVerificationMethod::kNone:
-      account_state = AccountState::kReady;
-      break;
-
-    case EnclaveUserVerificationMethod::kPIN:
-      account_state = AccountState::kReadyWithPIN;
-      break;
-
-    case EnclaveUserVerificationMethod::kUVKeyWithChromeUI:
-      account_state = AccountState::kReadyWithBiometrics;
-      break;
-
-    case EnclaveUserVerificationMethod::kUnsatisfiable:
-      account_state = AccountState::kNone;
-      break;
-  }
-
-  dialog_controller_->set_gpm_pin_is_arbitrary(
-      has_pin && enclave_manager_->wrapped_pin_is_arbitrary());
-  dialog_controller_->set_account_state(account_state);
-}
-
-void ChromeAuthenticatorRequestDelegate::OnAccountStateDownloaded(
-    std::unique_ptr<trusted_vault::TrustedVaultConnection> unused,
-    trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
-        result) {
-  using Result =
-      trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult;
-  download_account_state_request_.reset();
-  const char* state_str;
-  switch (result.state) {
-    case Result::State::kError:
-      state_str = "Error";
-      dialog_controller_->set_account_state(AccountState::kNone);
-      break;
-
-    case Result::State::kEmpty:
-      state_str = "Empty";
-      dialog_controller_->set_account_state(AccountState::kEmpty);
-      break;
-
-    case Result::State::kRecoverable:
-      state_str = "Recoverable";
-      dialog_controller_->set_account_state(AccountState::kRecoverable);
-      break;
-
-    case Result::State::kIrrecoverable:
-      state_str = "Irrecoverable";
-      dialog_controller_->set_account_state(AccountState::kIrrecoverable);
-      break;
-  }
-
-  FIDO_LOG(EVENT) << "Download account state result: " << state_str
-                  << ", key_version: " << result.key_version.value_or(0)
-                  << ", has PIN: " << result.gpm_pin_metadata.has_value();
-
-  if (result.gpm_pin_metadata) {
-    pin_metadata_ = std::move(result.gpm_pin_metadata);
-  }
-}
-
-void ChromeAuthenticatorRequestDelegate::MaybeHashPinAndStartEnclaveTransaction(
-    std::optional<std::string> token) {
-  std::string pin = gpm_pin_stashed_.has_value()
-                        ? std::move(*gpm_pin_stashed_)
-                        : dialog_controller_->TakeGPMPin();
-  gpm_pin_stashed_.reset();
-  if (pin.empty()) {
-    StartEnclaveTransaction(std::move(token), nullptr);
-    return;
-  }
-
-  base::ThreadPool::PostTaskAndReplyWithResult(
-      FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
-      base::BindOnce(
-          [](std::string pin,
-             std::unique_ptr<webauthn_pb::EnclaveLocalState_WrappedPIN>
-                 wrapped_pin) -> std::unique_ptr<device::enclave::ClaimedPIN> {
-            return EnclaveManager::MakeClaimedPINSlowly(std::move(pin),
-                                                        std::move(wrapped_pin));
-          },
-          std::move(pin), enclave_manager_->GetWrappedPIN()),
-      base::BindOnce(
-          &ChromeAuthenticatorRequestDelegate::StartEnclaveTransaction,
-          weak_ptr_factory_.GetWeakPtr(), std::move(token)));
-}
-
-void ChromeAuthenticatorRequestDelegate::StartEnclaveTransaction(
-    std::optional<std::string> token,
-    std::unique_ptr<device::enclave::ClaimedPIN> claimed_pin) {
-  CHECK(request_type_);
-  CHECK(user_verification_requirement_);
-  // The UI has advanced to the point where it wants to perform an enclave
-  // transaction. This code collects the needed values and triggers
-  // `enclave_request_callback_` which surfaces in
-  // `EnclaveDiscovery::OnUIRequest`.
-
-  auto request = std::make_unique<device::enclave::CredentialRequest>();
-
-  switch (PickEnclaveUserVerificationMethod(*user_verification_requirement_,
-                                            enclave_manager_->has_wrapped_pin(),
-                                            enclave_manager_->uv_key_state())) {
-    case EnclaveUserVerificationMethod::kNone:
-      request->signing_callback =
-          enclave_manager_->HardwareKeySigningCallback();
-      break;
-
-    case EnclaveUserVerificationMethod::kPIN:
-      request->signing_callback =
-          enclave_manager_->HardwareKeySigningCallback();
-      CHECK(claimed_pin);
-      request->claimed_pin = std::move(claimed_pin);
-      break;
-
-    case EnclaveUserVerificationMethod::kUVKeyWithChromeUI:
-    case EnclaveUserVerificationMethod::kUVKeyWithSystemUI: {
-      EnclaveManager::UVKeyOptions uv_options;
-#if BUILDFLAG(IS_MAC)
-      uv_options.lacontext = dialog_controller_->TakeLAContext();
-#endif  // BUILDFLAG(IS_MAC)
-      request->signing_callback =
-          enclave_manager_->UserVerifyingKeySigningCallback(
-              std::move(uv_options));
-      break;
-    }
-    case EnclaveUserVerificationMethod::kUnsatisfiable:
-      NOTREACHED_NORETURN();
-  }
-
-  // If fetching the access token failed a transaction is still started. Without
-  // a token it will fail, but that failure will trigger suitable error
-  // messages.
-  // TODO(enclave): jump directly to some future error state instead.
-  if (token) {
-    request->access_token = std::move(*token);
-  }
-
-  switch (*request_type_) {
-    case device::FidoRequestType::kMakeCredential: {
-      int32_t version;
-      std::vector<uint8_t> wrapped_secret;
-      std::tie(version, wrapped_secret) =
-          enclave_manager_->GetCurrentWrappedSecret();
-      request->wrapped_secret_version = version;
-      request->wrapped_secrets.emplace_back(std::move(wrapped_secret));
-      break;
-    }
-
-    case device::FidoRequestType::kGetAssertion: {
-      std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> entity;
-      for (const auto& cred : *gpm_credentials_) {
-        if (base::ranges::equal(
-                base::as_bytes(base::make_span(cred.credential_id())),
-                base::make_span(*preselected_cred_id_))) {
-          entity = std::make_unique<sync_pb::WebauthnCredentialSpecifics>(cred);
-          break;
-        }
-      }
-      CHECK(entity);
-
-      if (entity->key_version()) {
-        std::optional<std::vector<uint8_t>> wrapped_secret =
-            enclave_manager_->GetWrappedSecret(entity->key_version());
-        if (wrapped_secret) {
-          request->wrapped_secrets.emplace_back(std::move(*wrapped_secret));
-        } else {
-          FIDO_LOG(ERROR)
-              << "Unexpectedly did not have a wrapped key for epoch "
-              << entity->key_version();
-        }
-      }
-      if (request->wrapped_secrets.empty()) {
-        request->wrapped_secrets = enclave_manager_->GetWrappedSecrets();
-      }
-      CHECK(!request->wrapped_secrets.empty());
-
-      request->entity = std::move(entity);
-      break;
-    }
-  }
-
-  enclave_request_callback_.Run(std::move(request));
+  ShowUI(std::move(*pending_transport_availability_info));
 }
 
 bool ChromeAuthenticatorRequestDelegate::ShouldPermitCableExtension(
@@ -1680,11 +1290,8 @@
   // If `gpm_credentials_` is set then the sync entities have already been
   // fetched and should be reused so that a consistent set of entities is used
   // throughout.
-  const bool use_gpm_credentials =
-      gpm_credentials_.has_value() &&
-      base::FeatureList::IsEnabled(device::kWebAuthnEnclaveAuthenticator);
   std::vector<sync_pb::WebauthnCredentialSpecifics> credentials_vec;
-  if (!use_gpm_credentials) {
+  if (!enclave_controller_) {
     webauthn::PasskeyModel* passkey_model =
         PasskeyModelFactory::GetInstance()->GetForProfile(
             Profile::FromBrowserContext(GetBrowserContext()));
@@ -1694,8 +1301,8 @@
   }
 
   const std::vector<sync_pb::WebauthnCredentialSpecifics>& credentials =
-      use_gpm_credentials ? *gpm_credentials_ : credentials_vec;
-  device::AuthenticatorType type = enclave_manager_
+      enclave_controller_ ? enclave_controller_->creds() : credentials_vec;
+  device::AuthenticatorType type = enclave_controller_
                                        ? device::AuthenticatorType::kEnclave
                                        : device::AuthenticatorType::kPhone;
   for (const sync_pb::WebauthnCredentialSpecifics& passkey : credentials) {
@@ -1710,28 +1317,6 @@
   }
 }
 
-void ChromeAuthenticatorRequestDelegate::ConfigureEnclaveDiscovery(
-    const std::string& rp_id,
-    device::FidoDiscoveryFactory* discovery_factory) {
-  discovery_factory->set_enclave_passkey_creation_callback(
-      base::BindRepeating(&ChromeAuthenticatorRequestDelegate::OnPasskeyCreated,
-                          weak_ptr_factory_.GetWeakPtr()));
-
-  using EnclaveEventStream = device::FidoDiscoveryBase::EventStream<
-      std::unique_ptr<device::enclave::CredentialRequest>>;
-  std::unique_ptr<EnclaveEventStream> event_stream;
-  std::tie(enclave_request_callback_, event_stream) = EnclaveEventStream::New();
-  discovery_factory->set_enclave_ui_request_stream(std::move(event_stream));
-}
-
-void ChromeAuthenticatorRequestDelegate::OnPasskeyCreated(
-    sync_pb::WebauthnCredentialSpecifics passkey) {
-  webauthn::PasskeyModel* passkey_model =
-      PasskeyModelFactory::GetInstance()->GetForProfile(
-          Profile::FromBrowserContext(GetBrowserContext()));
-  passkey_model->CreatePasskey(passkey);
-}
-
 #if BUILDFLAG(IS_MAC)
 // static
 std::optional<int> ChromeAuthenticatorRequestDelegate::DaysSinceDate(
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.h b/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
index 49ec3dd..34f8447 100644
--- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
+++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
@@ -26,7 +26,7 @@
 #include "device/fido/fido_types.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 
-class EnclaveManager;
+class GPMEnclaveController;
 class PrefService;
 class Profile;
 
@@ -41,20 +41,8 @@
 class PublicKeyCredentialDescriptor;
 class PublicKeyCredentialUserEntity;
 enum class FidoRequestType : uint8_t;
-namespace enclave {
-struct ClaimedPIN;
-struct CredentialRequest;
-}
 }  // namespace device
 
-namespace signin {
-class PrimaryAccountAccessTokenFetcher;
-}  // namespace signin
-
-namespace sync_pb {
-class WebauthnCredentialSpecifics;
-}
-
 namespace user_prefs {
 class PrefRegistrySyncable;
 }
@@ -170,6 +158,8 @@
     return dialog_controller_.get();
   }
 
+  GPMEnclaveController* enclave_controller_for_testing() const;
+
   // content::AuthenticatorRequestClientDelegate:
   void SetRelyingPartyId(const std::string& rp_id) override;
   bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override;
@@ -232,7 +222,6 @@
   // AuthenticatorRequestDialogModel::Observer:
   void OnStartOver() override;
   void OnModelDestroyed(AuthenticatorRequestDialogModel* model) override;
-  void OnStepTransition() override;
   void OnCancelRequest() override;
   void OnManageDevicesClicked() override;
 
@@ -270,45 +259,7 @@
 
   std::optional<device::FidoTransportProtocol> GetLastTransportUsed() const;
 
-  // Called when the EnclaveManager has finished loading its state from the
-  // disk.
-  void OnEnclaveLoaded();
-
-  // Called when MagicArch has provided keys to the EnclaveManager.
-  void OnKeysStored();
-
-  // Called when the current device has been added to the security domain.
-  void OnDeviceAdded(bool success);
-
-  // Called when the user selects an account from modal or conditional UI.
-  // Stores the credential ID in `preselected_cred_id_` then forwards to the
-  // `AccountPreselectedCallback` that was passed to
-  // `RegisterActionCallbacks`.
-  void OnAccountPreselected(device::DiscoverableCredentialMetadata);
-
-  // Called to start fetching the state of the primary account from the
-  // trusted vault service.
-  void DownloadAccountState();
-
-  // Tell `dialog_model_` that the enclave manager is ready.
-  void SetAccountStateReady();
-
-  // Called when the state of the trusted vault has been determined by
-  // `DownloadAccountState`.
-  void OnAccountStateDownloaded(
-      std::unique_ptr<trusted_vault::TrustedVaultConnection> unused,
-      trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
-          result);
-
-  // Called when the UI has reached a state where it needs to do an enclave
-  // operation, and an OAuth token for the enclave has been fetched.
-  void MaybeHashPinAndStartEnclaveTransaction(std::optional<std::string> token);
-
-  // Called when the UI has reached a state where it needs to do an enclave
-  // operation, an OAuth token for the enclave has been fetched, and any PIN
-  // hashing has been completed.
-  void StartEnclaveTransaction(std::optional<std::string> token,
-                               std::unique_ptr<device::enclave::ClaimedPIN>);
+  void OnReadyForUI() override;
 
   // ShouldPermitCableExtension returns true if the given |origin| may set a
   // caBLE extension. This extension contains website-chosen BLE pairing
@@ -323,15 +274,6 @@
   void GetPhoneContactableGpmPasskeysForRpId(
       std::vector<device::DiscoverableCredentialMetadata>* passkeys);
 
-  // Configures an WebAuthn enclave authenticator discovery and provides it with
-  // synced passkeys.
-  void ConfigureEnclaveDiscovery(
-      const std::string& rp_id,
-      device::FidoDiscoveryFactory* discovery_factory);
-
-  // Invoked when a new GPM passkey is created, to save it to sync data.
-  void OnPasskeyCreated(sync_pb::WebauthnCredentialSpecifics passkey);
-
 #if BUILDFLAG(IS_MAC)
   // DaysSinceDate returns the number of days between `formatted_date` (in ISO
   // 8601 format) and `now`. It returns `nullopt` if `formatted_date` cannot be
@@ -402,15 +344,7 @@
   // available that can service requests for synced GPM passkeys.
   bool can_use_synced_phone_passkeys_ = false;
 
-  // Non-null when the cloud enclave authenticator is available for use. The
-  // `EnclaveManager` is a `KeyedService` for the current profile and so
-  // outlives this object.
-  raw_ptr<EnclaveManager> enclave_manager_ = nullptr;
-
-  // Used to observe `enclave_manager_` without implementing the Observer
-  // interface directly and thus needing to #include all of `enclave_manager.h`
-  // in this header file.
-  std::unique_ptr<EnclaveManagerObserver> enclave_manager_observer_;
+  std::unique_ptr<GPMEnclaveController> enclave_controller_;
 
   // Stores the TransportAvailabilityInfo while we're waiting for the enclave
   // state to load from the disk.
@@ -422,40 +356,10 @@
   std::optional<device::UserVerificationRequirement>
       user_verification_requirement_;
 
-  // The set of pertinent synced passkeys for this request. Persisted here
-  // so that a consistent set of passkeys is used throughout the transaction.
-  std::optional<std::vector<sync_pb::WebauthnCredentialSpecifics>>
-      gpm_credentials_;
-
-  // The pending request to fetch the state of the trusted vault.
-  std::unique_ptr<trusted_vault::TrustedVaultConnection::Request>
-      download_account_state_request_;
-
-  // The pending request to fetch an OAuth token for the enclave request.
-  std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher>
-      access_token_fetcher_;
-
-  // The callback used to trigger a request to the enclave.
-  base::RepeatingCallback<void(
-      std::unique_ptr<device::enclave::CredentialRequest>)>
-      enclave_request_callback_;
-
-  // The credential ID of the last credential to be selected by the user in
-  // modal or conditional UI.
-  std::optional<std::vector<uint8_t>> preselected_cred_id_;
-
-  // Contains the bytes of a WrappedPIN structure, downloaded from the security
-  // domain service.
-  std::optional<trusted_vault::GpmPinMetadata> pin_metadata_;
-
-  // Hold the GPM PIN in the special case where we prompt for a PIN to add one
-  // to the account, but then immediately need it in order to satisfy UV for
-  // the request.
-  std::optional<std::string> gpm_pin_stashed_;
-
-  // Override for test mocking.
+  // This holds a `TrustedVaultConnection` which will be set on
+  // `enclave_controller_` when it is created.
   std::unique_ptr<trusted_vault::TrustedVaultConnection>
-      vault_connection_override_;
+      pending_trusted_vault_connection_;
 
   base::WeakPtrFactory<ChromeAuthenticatorRequestDelegate> weak_ptr_factory_{
       this};
diff --git a/chrome/browser/webauthn/enclave_authenticator_browsertest.cc b/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
index 38c4e0c..ef7684d 100644
--- a/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
+++ b/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
@@ -29,6 +29,7 @@
 #include "chrome/browser/webauthn/enclave_manager.h"
 #include "chrome/browser/webauthn/enclave_manager_factory.h"
 #include "chrome/browser/webauthn/fake_security_domain_service.h"
+#include "chrome/browser/webauthn/gpm_enclave_controller.h"
 #include "chrome/browser/webauthn/passkey_model_factory.h"
 #include "chrome/browser/webauthn/test_util.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -569,8 +570,10 @@
 
   EXPECT_EQ(dialog_model()->step(),
             AuthenticatorRequestDialogModel::Step::kMechanismSelection);
-  EXPECT_EQ(request_delegate()->dialog_controller()->account_state(),
-            AuthenticatorRequestDialogController::AccountState::kEmpty);
+  EXPECT_EQ(request_delegate()
+                ->enclave_controller_for_testing()
+                ->account_state_for_testing(),
+            GPMEnclaveController::AccountState::kEmpty);
   model_observer()->SetStepToObserve(
       AuthenticatorRequestDialogController::Step::kGPMOnboarding);
   SimulateEnclaveMechanismSelection();
@@ -619,8 +622,10 @@
 
   EXPECT_EQ(dialog_model()->step(),
             AuthenticatorRequestDialogModel::Step::kSelectPriorityMechanism);
-  EXPECT_EQ(request_delegate()->dialog_controller()->account_state(),
-            AuthenticatorRequestDialogController::AccountState::kRecoverable);
+  EXPECT_EQ(request_delegate()
+                ->enclave_controller_for_testing()
+                ->account_state_for_testing(),
+            GPMEnclaveController::AccountState::kRecoverable);
   model_observer()->SetStepToObserve(
       AuthenticatorRequestDialogController::Step::kRecoverSecurityDomain);
   dialog_model()->OnUserConfirmedPriorityMechanism();
diff --git a/chrome/browser/webauthn/enclave_manager.cc b/chrome/browser/webauthn/enclave_manager.cc
index a8a6d47d..9f9a044 100644
--- a/chrome/browser/webauthn/enclave_manager.cc
+++ b/chrome/browser/webauthn/enclave_manager.cc
@@ -2513,8 +2513,11 @@
   }
 #if BUILDFLAG(IS_MAC)
   if (device::fido::mac::DeviceHasBiometricsAvailable()) {
-    // Chrome will display an LAAuthenticationView with a Touch ID prompt.
-    return UvKeyState::kUsesChromeUI;
+    // LAAuthenticationView is only supported on macOS 12+.
+    if (__builtin_available(macOS 12.0, *)) {
+      // Chrome will display an LAAuthenticationView with a Touch ID prompt.
+      return UvKeyState::kUsesChromeUI;
+    }
   }
   // Delegate prompting the user for their screen lock to macOS.
   return UvKeyState::kUsesSystemUI;
diff --git a/chrome/browser/webauthn/enclave_manager_unittest.cc b/chrome/browser/webauthn/enclave_manager_unittest.cc
index a5c8b50..76726e23 100644
--- a/chrome/browser/webauthn/enclave_manager_unittest.cc
+++ b/chrome/browser/webauthn/enclave_manager_unittest.cc
@@ -1112,7 +1112,14 @@
 
   scoped_fake_apple_keychain_.SetUVMethod(
       crypto::ScopedFakeAppleKeychainV2::UVMethod::kBiometrics);
-  EXPECT_EQ(manager_.uv_key_state(), EnclaveManager::UvKeyState::kUsesChromeUI);
+  // The TouchID view is only available on macOS 12+.
+  if (__builtin_available(macos 12, *)) {
+    EXPECT_EQ(manager_.uv_key_state(),
+              EnclaveManager::UvKeyState::kUsesChromeUI);
+  } else {
+    EXPECT_EQ(manager_.uv_key_state(),
+              EnclaveManager::UvKeyState::kUsesSystemUI);
+  }
 
   scoped_fake_apple_keychain_.SetUVMethod(
       crypto::ScopedFakeAppleKeychainV2::UVMethod::kPasswordOnly);
diff --git a/chrome/browser/webauthn/gpm_enclave_controller.cc b/chrome/browser/webauthn/gpm_enclave_controller.cc
new file mode 100644
index 0000000..578ab8af
--- /dev/null
+++ b/chrome/browser/webauthn/gpm_enclave_controller.cc
@@ -0,0 +1,753 @@
+// 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/webauthn/gpm_enclave_controller.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/thread_pool.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/webauthn/enclave_manager_factory.h"
+#include "chrome/browser/webauthn/passkey_model_factory.h"
+#include "chrome/browser/webauthn/proto/enclave_local_state.pb.h"
+#include "components/device_event_log/device_event_log.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
+#include "components/trusted_vault/frontend_trusted_vault_connection.h"
+#include "components/trusted_vault/trusted_vault_server_constants.h"
+#include "content/public/browser/render_frame_host.h"
+#include "device/fido/fido_discovery_factory.h"
+#include "device/fido/fido_types.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+using Step = AuthenticatorRequestDialogModel::Step;
+
+// These diagrams aren't exhaustive, but hopefully can help identify the control
+// flow in this code, which is very callback-heavy. The "digraph" sections are
+// the dot commands and the diagrams are generated from them with
+// https://dot-to-ascii.ggerganov.com/
+//
+//
+// create(), already enrolled
+//
+// digraph {
+//   OnGPMSelected -> kGPMCreatePasskey -> OnGPMCreatePasskey
+//   OnGPMCreatePasskey -> StartTransaction
+//   OnGPMCreatePasskey -> kGPMEnterPin -> OnGPMPinEntered ->
+//     StartTransaction
+//   OnGPMCreatePasskey -> kGPMTouchID -> OnTouchIDComplete ->
+//     StartTransaction
+// }
+//
+//                           +--------------------+
+//                           |   OnGPMSelected    |
+//                           +--------------------+
+//                             |
+//                             |
+//                             v
+//                           +--------------------+
+//                           | kGPMCreatePasskey  |
+//                           +--------------------+
+//                             |
+//                             |
+//                             v
+// +-------------------+     +--------------------+
+// |    kGPMTouchID    | <-- | OnGPMCreatePasskey | -+
+// +-------------------+     +--------------------+  |
+//   |                         |                     |
+//   |                         |                     |
+//   v                         v                     |
+// +-------------------+     +--------------------+  |
+// | OnTouchIDComplete |     |    kGPMEnterPin    |  |
+// +-------------------+     +--------------------+  |
+//   |                         |                     |
+//   |                         |                     |
+//   |                         v                     |
+//   |                       +--------------------+  |
+//   |                       |  OnGPMPinEntered   |  |
+//   |                       +--------------------+  |
+//   |                         |                     |
+//   |                         |                     |
+//   |                         v                     |
+//   |                       +--------------------+  |
+//   +---------------------> |  StartTransaction  | <+
+//                           +--------------------+
+
+// create(), empty security domain
+//
+// digraph {
+//   OnGPMSelected -> kGPMOnboarding -> OnGPMOnboardingAccepted ->
+//     kGPMCreatePin -> OnGPMPinEntered -> OnDeviceAdded
+//   OnDeviceAdded -> StartTransaction
+//   // TODO(enclave): it's bad that we could prompt for the PIN again
+//   // here
+//   OnDeviceAdded -> kGPMEnterPin -> OnGPMPinEntered -> StartTransaction
+//   OnDeviceAdded -> kGPMTouchID -> OnTouchIDComplete -> StartTransaction
+// }
+//
+//                      +-------------------------+
+//                      |      OnGPMSelected      |
+//                      +-------------------------+
+//                        |
+//                        |
+//                        v
+//                      +-------------------------+
+//                      |     kGPMOnboarding      |
+//                      +-------------------------+
+//                        |
+//                        |
+//                        v
+//                      +-------------------------+
+//                      | OnGPMOnboardingAccepted |
+//                      +-------------------------+
+//                        |
+//                        |
+//                        v
+//                      +-------------------------+
+//                      |      kGPMCreatePin      |
+//                      +-------------------------+
+//                        |
+//                        |
+//                        v
+//                      +-------------------------+
+//   +----------------> |     OnGPMPinEntered     | -+
+//   |                  +-------------------------+  |
+//   |                    |                          |
+//   |                    |                          |
+//   |                    v                          |
+// +--------------+     +-------------------------+  |
+// | kGPMEnterPin |  +- |      OnDeviceAdded      | -+----+
+// +--------------+  |  +-------------------------+  |    |
+//   ^               |    |                          |    |
+//   |               |    |                          |    |
+//   |               |    v                          |    |
+//   |               |  +-------------------------+  |    |
+//   |               |  |       kGPMTouchID       |  |    |
+//   |               |  +-------------------------+  |    |
+//   |               |    |                          |    |
+//   |               |    |                          |    |
+//   |               |    v                          |    |
+//   |               |  +-------------------------+  |    |
+//   |               |  |    OnTouchIDComplete    |  |    |
+//   |               |  +-------------------------+  |    |
+//   |               |    |                          |    |
+//   |               |    |                          |    |
+//   |               |    v                          |    |
+//   |               |  +-------------------------+  |    |
+//   |               +> |    StartTransaction     | <+    |
+//   |                  +-------------------------+       |
+//   |                                                    |
+//   +----------------------------------------------------+
+
+// get(), already enrolled
+//
+// digraph {
+//   OnGPMPasskeySelected -> StartTransaction
+//   OnGPMPasskeySelected -> kGPMEnterPin -> OnGPMPinEntered ->
+//     StartTransaction
+//   OnGPMPasskeySelected -> kGPMTouchID -> OnTouchIDComplete ->
+//     StartTransaction
+//
+// +-------------------+     +----------------------+
+// |    kGPMTouchID    | <-- | OnGPMPasskeySelected | -+
+// +-------------------+     +----------------------+  |
+//   |                         |                       |
+//   |                         |                       |
+//   v                         v                       |
+// +-------------------+     +----------------------+  |
+// | OnTouchIDComplete |     |     kGPMEnterPin     |  |
+// +-------------------+     +----------------------+  |
+//   |                         |                       |
+//   |                         |                       |
+//   |                         v                       |
+//   |                       +----------------------+  |
+//   |                       |   OnGPMPinEntered    |  |
+//   |                       +----------------------+  |
+//   |                         |                       |
+//   |                         |                       |
+//   |                         v                       |
+//   |                       +----------------------+  |
+//   +---------------------> |   StartTransaction   | <+
+//                           +----------------------+
+
+namespace {
+
+// EnclaveUserVerificationMethod enumerates the possible ways that user
+// verification will be performed for an enclave transaction.
+enum class EnclaveUserVerificationMethod {
+  // No user verification will be performed.
+  kNone,
+  // The user will enter a GPM PIN.
+  kPIN,
+  // The operating system will perform user verification and allow signing
+  // with the UV key.
+  kUVKeyWithSystemUI,
+  // Chrome will show user verification UI for the operating system, which will
+  // then allow signing
+  // with the UV key.
+  kUVKeyWithChromeUI,
+  // The request cannot be satisfied.
+  kUnsatisfiable,
+};
+
+// Pick an enclave user verification method for a specific request.
+EnclaveUserVerificationMethod PickEnclaveUserVerificationMethod(
+    device::UserVerificationRequirement uv,
+    bool has_pin,
+    EnclaveManager::UvKeyState uv_key_state) {
+  switch (uv) {
+    case device::UserVerificationRequirement::kDiscouraged:
+      return EnclaveUserVerificationMethod::kNone;
+
+    case device::UserVerificationRequirement::kPreferred:
+    case device::UserVerificationRequirement::kRequired:
+      switch (uv_key_state) {
+        case EnclaveManager::UvKeyState::kNone:
+          if (has_pin) {
+            return EnclaveUserVerificationMethod::kPIN;
+          } else if (uv == device::UserVerificationRequirement::kPreferred) {
+            return EnclaveUserVerificationMethod::kNone;
+          } else {
+            return EnclaveUserVerificationMethod::kUnsatisfiable;
+          }
+
+        case EnclaveManager::UvKeyState::kUsesSystemUI:
+          return EnclaveUserVerificationMethod::kUVKeyWithSystemUI;
+
+        case EnclaveManager::UvKeyState::kUsesChromeUI:
+          return EnclaveUserVerificationMethod::kUVKeyWithChromeUI;
+      }
+  }
+}
+
+}  // namespace
+
+GPMEnclaveController::GPMEnclaveController(
+    content::RenderFrameHost* render_frame_host,
+    AuthenticatorRequestDialogModel* model,
+    const std::string& rp_id,
+    device::FidoRequestType request_type,
+    device::UserVerificationRequirement user_verification_requirement)
+    : render_frame_host_id_(render_frame_host->GetGlobalId()),
+      rp_id_(rp_id),
+      request_type_(request_type),
+      user_verification_requirement_(user_verification_requirement),
+      enclave_manager_(EnclaveManagerFactory::GetForProfile(
+          Profile::FromBrowserContext(render_frame_host->GetBrowserContext()))),
+      model_(model) {
+  enclave_manager_observer_.Observe(enclave_manager_);
+  model_observer_.Observe(&model_->observers);
+
+  Profile* const profile =
+      Profile::FromBrowserContext(render_frame_host->GetBrowserContext());
+  webauthn::PasskeyModel* passkey_model =
+      PasskeyModelFactory::GetInstance()->GetForProfile(profile);
+  creds_ = passkey_model->GetPasskeysForRelyingPartyId(rp_id_);
+
+  if (creds_.empty() &&
+      request_type == device::FidoRequestType::kGetAssertion) {
+    // No possibility of using GPM for this request.
+    FIDO_LOG(EVENT) << "Enclave is not a candidate for this request";
+  } else if (enclave_manager_->is_ready()) {
+    FIDO_LOG(EVENT) << "Enclave is ready";
+    SetAccountStateReady();
+  } else if (enclave_manager_->is_loaded()) {
+    FIDO_LOG(EVENT) << "Account state needs to be checked";
+    account_state_ = AccountState::kChecking;
+    DownloadAccountState(profile);
+  } else {
+    FIDO_LOG(EVENT) << "Enclave state is loading";
+    account_state_ = AccountState::kLoading;
+    enclave_manager_->Load(
+        base::BindOnce(&GPMEnclaveController::OnEnclaveLoaded,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
+GPMEnclaveController::~GPMEnclaveController() = default;
+
+bool GPMEnclaveController::ready_for_ui() const {
+  return account_state_ != AccountState::kLoading;
+}
+
+void GPMEnclaveController::ConfigureDiscoveries(
+    device::FidoDiscoveryFactory* discovery_factory) {
+  discovery_factory->set_enclave_passkey_creation_callback(base::BindRepeating(
+      &GPMEnclaveController::OnPasskeyCreated, weak_ptr_factory_.GetWeakPtr()));
+
+  using EnclaveEventStream = device::FidoDiscoveryBase::EventStream<
+      std::unique_ptr<device::enclave::CredentialRequest>>;
+  std::unique_ptr<EnclaveEventStream> event_stream;
+  std::tie(enclave_request_callback_, event_stream) = EnclaveEventStream::New();
+  discovery_factory->set_enclave_ui_request_stream(std::move(event_stream));
+}
+
+const std::vector<sync_pb::WebauthnCredentialSpecifics>&
+GPMEnclaveController::creds() const {
+  return creds_;
+}
+
+void GPMEnclaveController::SetTrustedVaultConnectionForTesting(
+    std::unique_ptr<trusted_vault::TrustedVaultConnection> connection) {
+  vault_connection_override_ = std::move(connection);
+}
+
+Profile* GPMEnclaveController::GetProfile() const {
+  return Profile::FromBrowserContext(
+      content::RenderFrameHost::FromID(render_frame_host_id_)
+          ->GetBrowserContext());
+}
+
+GPMEnclaveController::AccountState
+GPMEnclaveController::account_state_for_testing() const {
+  return account_state_;
+}
+
+void GPMEnclaveController::OnEnclaveLoaded() {
+  CHECK_EQ(account_state_, AccountState::kLoading);
+
+  if (enclave_manager_->is_ready()) {
+    FIDO_LOG(EVENT) << "Enclave is ready";
+    SetAccountStateReady();
+  } else {
+    FIDO_LOG(EVENT) << "Account state needs to be checked";
+    account_state_ = AccountState::kChecking;
+    DownloadAccountState(GetProfile());
+  }
+
+  model_->OnReadyForUI();
+}
+
+void GPMEnclaveController::DownloadAccountState(Profile* profile) {
+  auto* const identity_manager = IdentityManagerFactory::GetForProfile(profile);
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
+      SystemNetworkContextManager::GetInstance()->GetSharedURLLoaderFactory();
+  std::unique_ptr<trusted_vault::TrustedVaultConnection> trusted_vault_conn =
+      vault_connection_override_
+          ? std::move(vault_connection_override_)
+          : trusted_vault::NewFrontendTrustedVaultConnection(
+                trusted_vault::SecurityDomainId::kPasskeys, identity_manager,
+                url_loader_factory);
+  auto* conn = trusted_vault_conn.get();
+  download_account_state_request_ =
+      conn->DownloadAuthenticationFactorsRegistrationState(
+          identity_manager->GetPrimaryAccountInfo(
+              signin::ConsentLevel::kSignin),
+          base::BindOnce(&GPMEnclaveController::OnAccountStateDownloaded,
+                         weak_ptr_factory_.GetWeakPtr(),
+                         std::move(trusted_vault_conn)));
+}
+
+void GPMEnclaveController::OnAccountStateDownloaded(
+    std::unique_ptr<trusted_vault::TrustedVaultConnection> unused,
+    trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
+        result) {
+  using Result =
+      trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult;
+  download_account_state_request_.reset();
+
+  const char* state_str;
+  switch (result.state) {
+    case Result::State::kError:
+      state_str = "Error";
+      account_state_ = AccountState::kNone;
+      break;
+
+    case Result::State::kEmpty:
+      state_str = "Empty";
+      account_state_ = AccountState::kEmpty;
+      break;
+
+    case Result::State::kRecoverable:
+      state_str = "Recoverable";
+      account_state_ = AccountState::kRecoverable;
+      break;
+
+    case Result::State::kIrrecoverable:
+      state_str = "Irrecoverable";
+      account_state_ = AccountState::kIrrecoverable;
+      break;
+  }
+
+  FIDO_LOG(EVENT) << "Download account state result: " << state_str
+                  << ", key_version: " << result.key_version.value_or(0)
+                  << ", has PIN: " << result.gpm_pin_metadata.has_value();
+
+  if (result.gpm_pin_metadata) {
+    pin_metadata_ = std::move(result.gpm_pin_metadata);
+  }
+
+  if (waiting_for_account_state_to_start_enclave_) {
+    waiting_for_account_state_to_start_enclave_ = false;
+    OnGPMSelected();
+  }
+}
+
+void GPMEnclaveController::OnKeysStored() {
+  if (model_->step() != Step::kRecoverSecurityDomain) {
+    return;
+  }
+  CHECK(enclave_manager_->has_pending_keys());
+  CHECK(!enclave_manager_->is_ready());
+
+  if (pin_metadata_.has_value()) {
+    // The account already has a GPM PIN.
+    if (!enclave_manager_->AddDeviceToAccount(
+            std::move(pin_metadata_),
+            base::BindOnce(&GPMEnclaveController::OnDeviceAdded,
+                           weak_ptr_factory_.GetWeakPtr()))) {
+      // TODO(enclave): move the UI to a to-be-created error state.
+      NOTREACHED();
+    }
+  } else {
+    // If the user has local biometrics, and an existing recovery factor,
+    // we'll likely choose not to create a GPM PIN. For now, however, we
+    // always do:
+    model_->SetStep(Step::kGPMCreatePin);
+  }
+}
+
+void GPMEnclaveController::OnDeviceAdded(bool success) {
+  if (!success) {
+    // TODO(enclave): move the UI to a to-be-created error state.
+    NOTREACHED();
+    return;
+  }
+
+  SetAccountStateReady();
+
+  switch (account_state_) {
+    case AccountState::kReady:
+      StartTransaction();
+      break;
+
+    case AccountState::kReadyWithPIN:
+      PromptForPin();
+      break;
+
+    case AccountState::kReadyWithBiometrics:
+      model_->SetStep(Step::kGPMTouchID);
+      break;
+
+    default:
+      NOTREACHED_NORETURN();
+  }
+}
+
+void GPMEnclaveController::SetAccountStateReady() {
+  switch (PickEnclaveUserVerificationMethod(user_verification_requirement_,
+                                            enclave_manager_->has_wrapped_pin(),
+                                            enclave_manager_->uv_key_state())) {
+    case EnclaveUserVerificationMethod::kUVKeyWithSystemUI:
+    case EnclaveUserVerificationMethod::kNone:
+      account_state_ = AccountState::kReady;
+      break;
+
+    case EnclaveUserVerificationMethod::kPIN:
+      account_state_ = AccountState::kReadyWithPIN;
+      break;
+
+    case EnclaveUserVerificationMethod::kUVKeyWithChromeUI:
+      account_state_ = AccountState::kReadyWithBiometrics;
+      break;
+
+    case EnclaveUserVerificationMethod::kUnsatisfiable:
+      account_state_ = AccountState::kNone;
+      break;
+  }
+
+  pin_is_arbitrary_ = enclave_manager_->has_wrapped_pin() &&
+                      enclave_manager_->wrapped_pin_is_arbitrary();
+}
+
+void GPMEnclaveController::OnGPMSelected() {
+  switch (account_state_) {
+    case AccountState::kReady:
+    case AccountState::kReadyWithPIN:
+      model_->SetStep(Step::kGPMCreatePasskey);
+      break;
+
+    case AccountState::kReadyWithBiometrics:
+      model_->SetStep(Step::kGPMTouchID);
+      break;
+
+    case AccountState::kRecoverable:
+      model_->SetStep(Step::kTrustThisComputer);
+      break;
+
+    case AccountState::kLoading:
+    case AccountState::kChecking:
+      waiting_for_account_state_to_start_enclave_ = true;
+      model_->ui_disabled_ = true;
+      model_->OnSheetModelChanged();
+      break;
+
+    case AccountState::kNone:
+      NOTREACHED();
+      break;
+
+    case AccountState::kIrrecoverable:
+      // TODO(enclave): show the reset flow.
+      NOTIMPLEMENTED();
+      break;
+
+    case AccountState::kEmpty:
+      model_->SetStep(Step::kGPMOnboarding);
+      break;
+  }
+}
+
+void GPMEnclaveController::OnGPMPasskeySelected(
+    base::span<const uint8_t> credential_id) {
+  selected_cred_id_ = std::vector(credential_id.begin(), credential_id.end());
+
+  switch (account_state_) {
+    case AccountState::kReady:
+      StartTransaction();
+      break;
+
+    case AccountState::kReadyWithPIN:
+      PromptForPin();
+      break;
+
+    case AccountState::kReadyWithBiometrics:
+      model_->SetStep(Step::kGPMTouchID);
+      break;
+
+    case AccountState::kRecoverable:
+      if (model_->priority_phone_name.has_value()) {
+        model_->SetStep(Step::kTrustThisComputer);
+      } else {
+        model_->SetStep(Step::kRecoverSecurityDomain);
+      }
+      break;
+
+    case AccountState::kLoading:
+    case AccountState::kChecking:
+      // TODO(enclave): need to disable the UI elements.
+      NOTIMPLEMENTED();
+      break;
+
+    case AccountState::kNone:
+    case AccountState::kIrrecoverable:
+      if (model_->priority_phone_name.has_value()) {
+        model_->ContactPriorityPhone();
+      } else {
+        NOTIMPLEMENTED();
+      }
+      break;
+
+    case AccountState::kEmpty:
+      if (model_->priority_phone_name.has_value()) {
+        model_->ContactPriorityPhone();
+      } else {
+        // TODO(enclave): the security domain is empty but there were
+        // sync entities. Most like the security domain was reset without
+        // clearing the entities, thus they are unusable. We have not yet
+        // decided what the behaviour will be in this case.
+        NOTIMPLEMENTED();
+      }
+      break;
+  }
+}
+
+void GPMEnclaveController::PromptForPin() {
+  model_->SetStep(pin_is_arbitrary_ ? Step::kGPMEnterArbitraryPin
+                                    : Step::kGPMEnterPin);
+}
+
+void GPMEnclaveController::OnTrustThisComputer() {
+  CHECK_EQ(model_->step(), Step::kTrustThisComputer);
+  model_->SetStep(Step::kRecoverSecurityDomain);
+}
+
+void GPMEnclaveController::OnGPMOnboardingAccepted() {
+  DCHECK_EQ(model_->step(), Step::kGPMOnboarding);
+  model_->SetStep(Step::kGPMCreatePin);
+}
+
+void GPMEnclaveController::OnGPMPinOptionChanged(bool is_arbitrary) {
+  CHECK(model_->step() == Step::kGPMCreatePin ||
+        model_->step() == Step::kGPMCreateArbitraryPin);
+  model_->SetStep(is_arbitrary ? Step::kGPMCreateArbitraryPin
+                               : Step::kGPMCreatePin);
+}
+
+void GPMEnclaveController::OnGPMCreatePasskey() {
+  DCHECK_EQ(model_->step(), Step::kGPMCreatePasskey);
+  DCHECK(account_state_ == AccountState::kReady ||
+         account_state_ == AccountState::kReadyWithPIN ||
+         account_state_ == AccountState::kReadyWithBiometrics);
+  if (account_state_ == AccountState::kReady) {
+    StartTransaction();
+  } else if (account_state_ == AccountState::kReadyWithPIN) {
+    PromptForPin();
+  } else if (account_state_ == AccountState::kReadyWithBiometrics) {
+    model_->SetStep(Step::kGPMTouchID);
+  } else {
+    NOTREACHED_NORETURN();
+  }
+}
+
+void GPMEnclaveController::OnGPMPinEntered(const std::u16string& pin) {
+  DCHECK(model_->step() == Step::kGPMCreateArbitraryPin ||
+         model_->step() == Step::kGPMCreatePin ||
+         model_->step() == Step::kGPMEnterArbitraryPin ||
+         model_->step() == Step::kGPMEnterPin);
+  pin_ = base::UTF16ToUTF8(pin);
+
+  // TODO(enclave): jump to spinner state here? The PIN entry will still
+  // be showing so should, at least, be disabled.
+
+  if (account_state_ == AccountState::kRecoverable) {
+    CHECK(enclave_manager_->has_pending_keys());
+    // In this case, we were waiting for the user to create their GPM PIN.
+    enclave_manager_->AddDeviceAndPINToAccount(
+        *pin_, base::BindOnce(&GPMEnclaveController::OnDeviceAdded,
+                              weak_ptr_factory_.GetWeakPtr()));
+  } else if (account_state_ == AccountState::kEmpty) {
+    // The user has set a PIN to create the account.
+    enclave_manager_->SetupWithPIN(
+        *pin_, base::BindOnce(&GPMEnclaveController::OnDeviceAdded,
+                              weak_ptr_factory_.GetWeakPtr()));
+  } else {
+    StartTransaction();
+  }
+}
+
+void GPMEnclaveController::OnTouchIDComplete(bool success) {
+  // On error no LAContext will be provided and macOS will show the system UI
+  // for user verification.
+  StartTransaction();
+}
+
+void GPMEnclaveController::StartTransaction() {
+  access_token_fetcher_ = enclave_manager_->GetAccessToken(base::BindOnce(
+      &GPMEnclaveController::MaybeHashPinAndStartEnclaveTransaction,
+      weak_ptr_factory_.GetWeakPtr()));
+}
+
+void GPMEnclaveController::MaybeHashPinAndStartEnclaveTransaction(
+    std::optional<std::string> token) {
+  if (!pin_) {
+    StartEnclaveTransaction(std::move(token), nullptr);
+    return;
+  }
+
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
+      base::BindOnce(
+          [](std::string pin,
+             std::unique_ptr<webauthn_pb::EnclaveLocalState_WrappedPIN>
+                 wrapped_pin) -> std::unique_ptr<device::enclave::ClaimedPIN> {
+            return EnclaveManager::MakeClaimedPINSlowly(std::move(pin),
+                                                        std::move(wrapped_pin));
+          },
+          *pin_, enclave_manager_->GetWrappedPIN()),
+      base::BindOnce(&GPMEnclaveController::StartEnclaveTransaction,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(token)));
+}
+
+void GPMEnclaveController::StartEnclaveTransaction(
+    std::optional<std::string> token,
+    std::unique_ptr<device::enclave::ClaimedPIN> claimed_pin) {
+  // The UI has advanced to the point where it wants to perform an enclave
+  // transaction. This code collects the needed values and triggers
+  // `enclave_request_callback_` which surfaces in
+  // `EnclaveDiscovery::OnUIRequest`.
+
+  auto request = std::make_unique<device::enclave::CredentialRequest>();
+
+  switch (PickEnclaveUserVerificationMethod(user_verification_requirement_,
+                                            enclave_manager_->has_wrapped_pin(),
+                                            enclave_manager_->uv_key_state())) {
+    case EnclaveUserVerificationMethod::kNone:
+      request->signing_callback =
+          enclave_manager_->HardwareKeySigningCallback();
+      break;
+
+    case EnclaveUserVerificationMethod::kPIN:
+      request->signing_callback =
+          enclave_manager_->HardwareKeySigningCallback();
+      CHECK(claimed_pin);
+      request->claimed_pin = std::move(claimed_pin);
+      break;
+
+    case EnclaveUserVerificationMethod::kUVKeyWithChromeUI:
+    case EnclaveUserVerificationMethod::kUVKeyWithSystemUI: {
+      EnclaveManager::UVKeyOptions uv_options;
+#if BUILDFLAG(IS_MAC)
+      uv_options.lacontext = std::move(model_->lacontext);
+#endif  // BUILDFLAG(IS_MAC)
+      request->signing_callback =
+          enclave_manager_->UserVerifyingKeySigningCallback(
+              std::move(uv_options));
+      break;
+    }
+    case EnclaveUserVerificationMethod::kUnsatisfiable:
+      NOTREACHED_NORETURN();
+  }
+
+  // If fetching the access token failed a transaction is still started. Without
+  // a token it will fail, but that failure will trigger suitable error
+  // messages.
+  // TODO(enclave): jump directly to some future error state instead.
+  if (token) {
+    request->access_token = std::move(*token);
+  }
+
+  switch (request_type_) {
+    case device::FidoRequestType::kMakeCredential: {
+      int32_t version;
+      std::vector<uint8_t> wrapped_secret;
+      std::tie(version, wrapped_secret) =
+          enclave_manager_->GetCurrentWrappedSecret();
+      request->wrapped_secret_version = version;
+      request->wrapped_secrets.emplace_back(std::move(wrapped_secret));
+      break;
+    }
+
+    case device::FidoRequestType::kGetAssertion: {
+      std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> entity;
+      for (const auto& cred : creds_) {
+        if (base::ranges::equal(
+                base::as_bytes(base::make_span(cred.credential_id())),
+                base::make_span(*selected_cred_id_))) {
+          entity = std::make_unique<sync_pb::WebauthnCredentialSpecifics>(cred);
+          break;
+        }
+      }
+      CHECK(entity);
+
+      if (entity->key_version()) {
+        std::optional<std::vector<uint8_t>> wrapped_secret =
+            enclave_manager_->GetWrappedSecret(entity->key_version());
+        if (wrapped_secret) {
+          request->wrapped_secrets.emplace_back(std::move(*wrapped_secret));
+        } else {
+          FIDO_LOG(ERROR)
+              << "Unexpectedly did not have a wrapped key for epoch "
+              << entity->key_version();
+        }
+      }
+      if (request->wrapped_secrets.empty()) {
+        request->wrapped_secrets = enclave_manager_->GetWrappedSecrets();
+      }
+      CHECK(!request->wrapped_secrets.empty());
+
+      request->entity = std::move(entity);
+      break;
+    }
+  }
+
+  enclave_request_callback_.Run(std::move(request));
+}
+
+void GPMEnclaveController::OnPasskeyCreated(
+    sync_pb::WebauthnCredentialSpecifics passkey) {
+  webauthn::PasskeyModel* passkey_model =
+      PasskeyModelFactory::GetInstance()->GetForProfile(GetProfile());
+  passkey_model->CreatePasskey(passkey);
+}
diff --git a/chrome/browser/webauthn/gpm_enclave_controller.h b/chrome/browser/webauthn/gpm_enclave_controller.h
new file mode 100644
index 0000000..03f65cc
--- /dev/null
+++ b/chrome/browser/webauthn/gpm_enclave_controller.h
@@ -0,0 +1,213 @@
+// 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_WEBAUTHN_GPM_ENCLAVE_CONTROLLER_H_
+#define CHROME_BROWSER_WEBAUTHN_GPM_ENCLAVE_CONTROLLER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
+#include "chrome/browser/webauthn/enclave_manager.h"
+#include "components/trusted_vault/trusted_vault_connection.h"
+#include "content/public/browser/global_routing_id.h"
+
+namespace content {
+class RenderFrameHost;
+}  // namespace content
+
+namespace device {
+class FidoDiscoveryFactory;
+enum class FidoRequestType : uint8_t;
+enum class UserVerificationRequirement;
+namespace enclave {
+struct CredentialRequest;
+}  // namespace enclave
+}  // namespace device
+
+namespace signin {
+class PrimaryAccountAccessTokenFetcher;
+}  // namespace signin
+
+class Profile;
+
+class GPMEnclaveController : AuthenticatorRequestDialogModel::Observer,
+                             EnclaveManager::Observer {
+ public:
+  enum class AccountState {
+    // There isn't a primary account, or enclave support is disabled.
+    kNone,
+    // The enclave state is still being loaded from disk.
+    kLoading,
+    // The state of the account is unknown pending network requests.
+    kChecking,
+    // The account can be recovered via user action.
+    kRecoverable,
+    // The account cannot be recovered, but could be reset.
+    kIrrecoverable,
+    // The security domain is empty.
+    kEmpty,
+    // The enclave is ready to use.
+    kReady,
+    // The enclave is ready to use, but the UI needs to collect a PIN before
+    // making a transaction.
+    kReadyWithPIN,
+    // The enclave is ready to use, but the UI needs to collect biometrics
+    // before making a transaction.
+    kReadyWithBiometrics,
+  };
+
+  explicit GPMEnclaveController(
+      content::RenderFrameHost* render_frame_host,
+      AuthenticatorRequestDialogModel* model,
+      const std::string& rp_id,
+      device::FidoRequestType request_type,
+      device::UserVerificationRequirement user_verification_requirement);
+  GPMEnclaveController(const GPMEnclaveController&) = delete;
+  GPMEnclaveController& operator=(const GPMEnclaveController&) = delete;
+  GPMEnclaveController(GPMEnclaveController&&) = delete;
+  GPMEnclaveController& operator=(GPMEnclaveController&&) = delete;
+  ~GPMEnclaveController() override;
+
+  // Returns true if the enclave state is loaded to the point where the UI
+  // can be shown. If false, then the `OnReadyForUI` event will be triggered
+  // on the model when ready.
+  bool ready_for_ui() const;
+
+  // Configures an WebAuthn enclave authenticator discovery and provides it with
+  // synced passkeys.
+  void ConfigureDiscoveries(device::FidoDiscoveryFactory* discovery_factory);
+
+  // Fetch the set of GPM passkeys for this request.
+  const std::vector<sync_pb::WebauthnCredentialSpecifics>& creds() const;
+
+  // Allows setting a mock `TrustedVaultConnection` so a real one will not be
+  // created. This is only used for a single request, and is destroyed
+  // afterwards.
+  void SetTrustedVaultConnectionForTesting(
+      std::unique_ptr<trusted_vault::TrustedVaultConnection> connection);
+
+  AccountState account_state_for_testing() const;
+
+ private:
+  Profile* GetProfile() const;
+
+  // Called when the EnclaveManager has finished loading its state from the
+  // disk.
+  void OnEnclaveLoaded();
+
+  // Starts downloading the state of the account from the security domain
+  // service.
+  void DownloadAccountState(Profile* profile);
+
+  // Called when the account state has finished downloading.
+  void OnAccountStateDownloaded(
+      std::unique_ptr<trusted_vault::TrustedVaultConnection> unused,
+      trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
+          result);
+
+  // EnclaveManager::Observer:
+  void OnKeysStored() override;
+
+  // Called when the local device has been added to the security domain.
+  void OnDeviceAdded(bool success);
+
+  // Called when the EnclaveManager is ready. Sets `account_state_` to the
+  // correct value for the level of user verification required.
+  void SetAccountStateReady();
+
+  // Called when the user selects Google Password Manager from the list of
+  // mechanisms. (Or when it's the priority mechanism.)
+  void OnGPMSelected() override;
+
+  // Called when a GPM passkey is selected from a list of credentials.
+  void OnGPMPasskeySelected(base::span<const uint8_t> credential_id) override;
+
+  // Sets the UI to the correct PIN prompt for the type of PIN configured.
+  void PromptForPin();
+
+  // AuthenticatorRequestDialogModel::Observer:
+  void OnTrustThisComputer() override;
+  void OnGPMOnboardingAccepted() override;
+  void OnGPMPinOptionChanged(bool is_arbitrary) override;
+  void OnGPMCreatePasskey() override;
+  void OnGPMPinEntered(const std::u16string& pin) override;
+  void OnTouchIDComplete(bool success) override;
+
+  // Starts a create() or get() action with the enclave.
+  void StartTransaction();
+
+  // Called when the UI has reached a state where it needs to do an enclave
+  // operation, and an OAuth token for the enclave has been fetched.
+  void MaybeHashPinAndStartEnclaveTransaction(std::optional<std::string> token);
+
+  // Called when the UI has reached a state where it needs to do an enclave
+  // operation, an OAuth token for the enclave has been fetched, and any PIN
+  // hashing has been completed.
+  void StartEnclaveTransaction(std::optional<std::string> token,
+                               std::unique_ptr<device::enclave::ClaimedPIN>);
+
+  // Invoked when a new GPM passkey is created, to save it to sync data.
+  void OnPasskeyCreated(sync_pb::WebauthnCredentialSpecifics passkey);
+
+  const content::GlobalRenderFrameHostId render_frame_host_id_;
+  const std::string rp_id_;
+  const device::FidoRequestType request_type_;
+  const device::UserVerificationRequirement user_verification_requirement_;
+
+  // The `EnclaveManager` is a `KeyedService` for the current profile and so
+  // outlives this object.
+  const raw_ptr<EnclaveManager> enclave_manager_;
+
+  // This is owned by the ChromeAuthenticatorRequestDelegate, which also owns
+  // this object.
+  const raw_ptr<AuthenticatorRequestDialogModel> model_;
+
+  base::ScopedObservation<
+      base::ObserverList<AuthenticatorRequestDialogModel::Observer>,
+      AuthenticatorRequestDialogModel::Observer>
+      model_observer_{this};
+  base::ScopedObservation<EnclaveManager, EnclaveManager::Observer>
+      enclave_manager_observer_{this};
+
+  AccountState account_state_ = AccountState::kNone;
+  bool pin_is_arbitrary_ = false;
+  std::optional<std::string> pin_;
+  std::vector<sync_pb::WebauthnCredentialSpecifics> creds_;
+
+  // The ID of the selected credential when doing a get().
+  std::optional<std::vector<uint8_t>> selected_cred_id_;
+
+  // Contains the bytes of a WrappedPIN structure, downloaded from the security
+  // domain service.
+  std::optional<trusted_vault::GpmPinMetadata> pin_metadata_;
+
+  // The pending request to fetch the state of the trusted vault.
+  std::unique_ptr<trusted_vault::TrustedVaultConnection::Request>
+      download_account_state_request_;
+
+  // The pending request to fetch an OAuth token for the enclave request.
+  std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher>
+      access_token_fetcher_;
+
+  // The callback used to trigger a request to the enclave.
+  base::RepeatingCallback<void(
+      std::unique_ptr<device::enclave::CredentialRequest>)>
+      enclave_request_callback_;
+
+  // Override for test mocking.
+  std::unique_ptr<trusted_vault::TrustedVaultConnection>
+      vault_connection_override_;
+
+  // Whether showing the UI was delayed because the result from the security
+  // domain service is needed.
+  bool waiting_for_account_state_to_start_enclave_ = false;
+
+  base::WeakPtrFactory<GPMEnclaveController> weak_ptr_factory_{this};
+};
+
+#endif  // CHROME_BROWSER_WEBAUTHN_GPM_ENCLAVE_CONTROLLER_H_
diff --git a/chrome/browser/webid/federated_identity_account_keyed_permission_context.cc b/chrome/browser/webid/federated_identity_account_keyed_permission_context.cc
index 09f52ad0..89dd904e 100644
--- a/chrome/browser/webid/federated_identity_account_keyed_permission_context.cc
+++ b/chrome/browser/webid/federated_identity_account_keyed_permission_context.cc
@@ -7,9 +7,11 @@
 #include <optional>
 
 #include "base/functional/callback_helpers.h"
+#include "base/json/values_util.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/time/default_clock.h"
 #include "base/values.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -24,22 +26,12 @@
 #include "url/origin.h"
 
 namespace {
-const char kAccountIdsKey[] = "account-ids";
-const char kRpRequesterKey[] = "rp-requester";
-const char kRpEmbedderKey[] = "rp-embedder";
-const char kSharingIdpKey[] = "idp-origin";
-
-void AddToAccountList(base::Value::Dict& dict, const std::string& account_id) {
-  base::Value::List* account_list = dict.FindList(kAccountIdsKey);
-  if (account_list) {
-    account_list->Append(account_id);
-    return;
-  }
-
-  base::Value::List new_list;
-  new_list.Append(account_id);
-  dict.Set(kAccountIdsKey, base::Value(std::move(new_list)));
-}
+constexpr char kAccountIdsKey[] = "account-ids";
+constexpr char kRpRequesterKey[] = "rp-requester";
+constexpr char kRpEmbedderKey[] = "rp-embedder";
+constexpr char kSharingIdpKey[] = "idp-origin";
+constexpr char kAccountIdKey[] = "account-id";
+constexpr char kTimestampKey[] = "timestamp";
 
 std::string BuildKey(const std::optional<std::string>& relying_party_requester,
                      const std::optional<std::string>& relying_party_embedder,
@@ -64,6 +56,23 @@
                   identity_provider.Serialize());
 }
 
+base::Value::List::iterator FindAccount(base::Value::List& account_list,
+                                        const std::string& account_id) {
+  return std::find_if(account_list.begin(), account_list.end(),
+                      [&account_id](const auto& value) {
+                        if (value.is_string()) {
+                          return value.GetString() == account_id;
+                        } else if (value.is_dict()) {
+                          const std::string* id =
+                              value.GetDict().FindString(kAccountIdKey);
+                          return id && *id == account_id;
+                        }
+                        // This is currently unreachable but we just bail out if
+                        // it is not a string or dict to future-proof the code.
+                        return false;
+                      });
+}
+
 }  // namespace
 
 FederatedIdentityAccountKeyedPermissionContext::
@@ -82,7 +91,8 @@
           ContentSettingsType::FEDERATED_IDENTITY_SHARING,
           host_content_settings_map),
       browser_context_(
-          raw_ref<content::BrowserContext>::from_ptr(browser_context)) {}
+          raw_ref<content::BrowserContext>::from_ptr(browser_context)),
+      clock_(base::DefaultClock::GetInstance()) {}
 
 bool FederatedIdentityAccountKeyedPermissionContext::HasPermission(
     const url::Origin& relying_party_requester) {
@@ -130,26 +140,23 @@
   // than origin, and also ensure that duplicate sites cannot be added.
   std::string key = BuildKey(relying_party_requester, relying_party_embedder,
                              identity_provider);
-  auto granted_object = GetGrantedObject(relying_party_requester, key);
+  const auto granted_object = GetGrantedObject(relying_party_requester, key);
 
-  if (!granted_object)
+  if (!granted_object) {
     return false;
+  }
 
-  const base::Value::List* account_list =
+  base::Value::List* account_list =
       granted_object->value.FindList(kAccountIdsKey);
-  if (!account_list)
+  if (!account_list) {
     return false;
+  }
 
   if (!account_id) {
     return true;
   }
 
-  for (auto& account_id_value : *account_list) {
-    if (account_id_value.GetString() == account_id)
-      return true;
-  }
-
-  return false;
+  return FindAccount(*account_list, *account_id) != account_list->end();
 }
 
 void FederatedIdentityAccountKeyedPermissionContext::GrantPermission(
@@ -157,10 +164,10 @@
     const url::Origin& relying_party_embedder,
     const url::Origin& identity_provider,
     const std::string& account_id) {
-  if (HasPermission(relying_party_requester, relying_party_embedder,
-                    identity_provider, account_id))
+  if (RefreshExistingPermission(relying_party_requester, relying_party_embedder,
+                                identity_provider, account_id)) {
     return;
-
+  }
   std::string key = BuildKey(relying_party_requester, relying_party_embedder,
                              identity_provider);
   const auto granted_object = GetGrantedObject(relying_party_requester, key);
@@ -181,6 +188,45 @@
   SyncSharingPermissionGrantsToNetworkService(base::DoNothing());
 }
 
+bool FederatedIdentityAccountKeyedPermissionContext::RefreshExistingPermission(
+    const url::Origin& relying_party_requester,
+    const url::Origin& relying_party_embedder,
+    const url::Origin& identity_provider,
+    const std::string& account_id) {
+  std::string key = BuildKey(relying_party_requester, relying_party_embedder,
+                             identity_provider);
+  const auto granted_object = GetGrantedObject(relying_party_requester, key);
+  if (!granted_object) {
+    return false;
+  }
+
+  base::Value::Dict new_object = granted_object->value.Clone();
+  base::Value::List* account_list = new_object.FindList(kAccountIdsKey);
+  if (!account_list) {
+    return false;
+  }
+
+  auto it = FindAccount(*account_list, account_id);
+  if (it == account_list->end()) {
+    return false;
+  }
+  if (it->is_string()) {
+    // Erase the previous string, and add a list (id, timestamp).
+    account_list->erase(it);
+
+    base::Value::Dict account_dict;
+    account_dict.Set(kAccountIdKey, account_id);
+    account_dict.Set(kTimestampKey, base::TimeToValue(clock_->Now()));
+    account_list->Append(std::move(account_dict));
+  } else if (it->is_dict()) {
+    // Update the last used timestamp of the (id, timestamp) pair.
+    it->GetDict().Set(kTimestampKey, base::TimeToValue(clock_->Now()));
+  }
+  UpdateObjectPermission(relying_party_requester, granted_object->value,
+                         std::move(new_object));
+  return true;
+}
+
 void FederatedIdentityAccountKeyedPermissionContext::RevokePermission(
     const url::Origin& relying_party_requester,
     const url::Origin& relying_party_embedder,
@@ -190,13 +236,18 @@
   std::string key = BuildKey(relying_party_requester, relying_party_embedder,
                              identity_provider);
   const auto object = GetGrantedObject(relying_party_requester, key);
-  if (!object)
+  if (!object) {
+    std::move(callback).Run();
     return;
+  }
 
   base::Value::Dict new_object = object->value.Clone();
   base::Value::List* account_ids = new_object.FindList(kAccountIdsKey);
   if (account_ids) {
-    if (!account_ids->EraseValue(base::Value(account_id))) {
+    auto it = FindAccount(*account_ids, account_id);
+    if (it != account_ids->end()) {
+      account_ids->erase(it);
+    } else {
       account_ids->clear();
     }
   }
@@ -257,10 +308,17 @@
 
     CHECK(idp_origin && rp_requester_origin && rp_embedder_origin);
     for (const auto& account : *accounts) {
-      data_keys.emplace_back(url::Origin::Create(GURL(*rp_requester_origin)),
-                             url::Origin::Create(GURL(*rp_embedder_origin)),
-                             url::Origin::Create(GURL(*idp_origin)),
-                             account.GetString());
+      if (account.is_string()) {
+        data_keys.emplace_back(url::Origin::Create(GURL(*rp_requester_origin)),
+                               url::Origin::Create(GURL(*rp_embedder_origin)),
+                               url::Origin::Create(GURL(*idp_origin)),
+                               account.GetString());
+      } else if (account.is_dict()) {
+        data_keys.emplace_back(url::Origin::Create(GURL(*rp_requester_origin)),
+                               url::Origin::Create(GURL(*rp_embedder_origin)),
+                               url::Origin::Create(GURL(*idp_origin)),
+                               *account.GetDict().FindString(kAccountIdKey));
+      }
     }
   }
   std::move(callback).Run(std::move(data_keys));
@@ -329,3 +387,21 @@
                            GetSharingPermissionGrantsAsContentSettings(),
                            std::move(callback));
 }
+
+void FederatedIdentityAccountKeyedPermissionContext::AddToAccountList(
+    base::Value::Dict& dict,
+    const std::string& account_id) {
+  base::Value::Dict account_dict;
+  account_dict.Set(kAccountIdKey, account_id);
+  account_dict.Set(kTimestampKey, base::TimeToValue(clock_->Now()));
+
+  base::Value::List* account_list = dict.FindList(kAccountIdsKey);
+  if (account_list) {
+    account_list->Append(std::move(account_dict));
+    return;
+  }
+
+  base::Value::List new_list;
+  new_list.Append(std::move(account_dict));
+  dict.Set(kAccountIdsKey, base::Value(std::move(new_list)));
+}
diff --git a/chrome/browser/webid/federated_identity_account_keyed_permission_context.h b/chrome/browser/webid/federated_identity_account_keyed_permission_context.h
index f90ed7e..61f7def 100644
--- a/chrome/browser/webid/federated_identity_account_keyed_permission_context.h
+++ b/chrome/browser/webid/federated_identity_account_keyed_permission_context.h
@@ -68,6 +68,14 @@
                        const url::Origin& identity_provider,
                        const std::string& account_id);
 
+  // Updates the timestamp of an existing permission, or does nothing if no
+  // permission matching the key is found. Returns true if a permission is
+  // found, false otherwise.
+  bool RefreshExistingPermission(const url::Origin& relying_party_requester,
+                                 const url::Origin& relying_party_embedder,
+                                 const url::Origin& identity_provider,
+                                 const std::string& account_id);
+
   // Revokes previously-granted permission for the (`relying_party_requester`,
   // `relying_party_embedder`, `identity_provider`, `account_id`) tuple. If the
   // `account_id` is not found, we revoke all accounts associated with the
@@ -103,8 +111,12 @@
   // given network request or `document.cookie` evaluation.
   void SyncSharingPermissionGrantsToNetworkService(base::OnceClosure callback);
 
+  void AddToAccountList(base::Value::Dict& dict, const std::string& account_id);
+
   // The BrowserContext associated with this permission context.
   base::raw_ref<content::BrowserContext> browser_context_;
+
+  raw_ptr<base::Clock> clock_;
 };
 
 #endif  // CHROME_BROWSER_WEBID_FEDERATED_IDENTITY_ACCOUNT_KEYED_PERMISSION_CONTEXT_H_
diff --git a/chrome/browser/webid/federated_identity_account_keyed_permission_context_unittest.cc b/chrome/browser/webid/federated_identity_account_keyed_permission_context_unittest.cc
index 50e78fc2..2e611d89 100644
--- a/chrome/browser/webid/federated_identity_account_keyed_permission_context_unittest.cc
+++ b/chrome/browser/webid/federated_identity_account_keyed_permission_context_unittest.cc
@@ -243,7 +243,7 @@
 }
 
 // Test that granting a permission for an account, if the permission has already
-// been granted, is a noop.
+// been granted, is a noop, except for the timestamps.
 TEST_F(FederatedIdentityAccountKeyedPermissionContextTest,
        GrantPermissionForSameAccount) {
   const url::Origin rp_requester =
@@ -259,9 +259,41 @@
   context()->GrantPermission(rp_requester, rp_embedder, idp, account);
   auto granted_objects2 = context()->GetAllGrantedObjects();
 
-  EXPECT_EQ(1u, granted_objects1.size());
-  EXPECT_EQ(1u, granted_objects2.size());
-  EXPECT_EQ(granted_objects1[0]->value, granted_objects2[0]->value);
+  ASSERT_EQ(1u, granted_objects1.size());
+  ASSERT_EQ(1u, granted_objects2.size());
+
+  const std::string strs[] = {"idp-origin", "rp-embedder", "rp-requester"};
+  for (const auto& str : strs) {
+    std::string* str1 = granted_objects1[0]->value.FindString(str);
+    ASSERT_TRUE(str1);
+    std::string* str2 = granted_objects1[0]->value.FindString(str);
+    ASSERT_TRUE(str2);
+    EXPECT_EQ(*str1, *str2);
+  }
+
+  base::Value::List* account_list1 =
+      granted_objects1[0]->value.FindList("account-ids");
+  ASSERT_TRUE(account_list1);
+  ASSERT_EQ(account_list1->size(), 1u);
+  ASSERT_TRUE(account_list1->front().is_dict());
+  const auto& account_dict1 = account_list1->front().GetDict();
+  ASSERT_EQ(account_dict1.size(), 2u);
+  EXPECT_TRUE(account_dict1.FindString("account-id"));
+  EXPECT_TRUE(account_dict1.FindString("timestamp"));
+
+  base::Value::List* account_list2 =
+      granted_objects1[0]->value.FindList("account-ids");
+  ASSERT_TRUE(account_list2);
+  ASSERT_EQ(account_list2->size(), 1u);
+  ASSERT_TRUE(account_list2->front().is_dict());
+  const auto& account_dict2 = account_list2->front().GetDict();
+  ASSERT_EQ(account_dict2.size(), 2u);
+  EXPECT_TRUE(account_dict2.FindString("account-id"));
+  EXPECT_TRUE(account_dict2.FindString("timestamp"));
+
+  // The strings should match.
+  EXPECT_EQ(account_dict1.FindString("account-id"),
+            account_dict2.FindString("account-id"));
 }
 
 // Test that FederatedIdentityAccountKeyedPermissionContext can recover from
@@ -399,3 +431,104 @@
   EXPECT_THAT(context()->GetSharingPermissionGrantsAsContentSettings(),
               IsEmpty());
 }
+
+TEST_F(FederatedIdentityAccountKeyedPermissionContextTest,
+       PermissionWithAndWithoutTimestamp) {
+  const url::Origin relying_party_requester =
+      url::Origin::Create(GURL("https://www.relying_party_requester.com"));
+  const url::Origin relying_party_embedder =
+      url::Origin::Create(GURL("https://www.relying_party_embedder.com"));
+  const url::Origin identity_provider =
+      url::Origin::Create(GURL("https://www.identity_provider.com"));
+
+  const std::string account_a("conestogo");
+  const std::string account_b("woolwich");
+  const std::string account_c("wellesley");
+
+  // Add a couple of accounts without timestamps.
+  std::string key =
+      base::StringPrintf("%s<%s", identity_provider.Serialize().c_str(),
+                         relying_party_embedder.Serialize().c_str());
+
+  base::Value::Dict new_object;
+  new_object.Set("rp-requester", relying_party_requester.Serialize());
+  new_object.Set("rp-embedder", relying_party_embedder.Serialize());
+  new_object.Set("idp-origin", identity_provider.Serialize());
+
+  base::Value::List account_list;
+  account_list.Append(account_a);
+  account_list.Append(account_b);
+  new_object.Set("account-ids", base::Value(std::move(account_list)));
+  context()->GrantObjectPermission(relying_party_requester,
+                                   std::move(new_object));
+
+  EXPECT_TRUE(context()->HasPermission(relying_party_requester,
+                                       relying_party_embedder,
+                                       identity_provider, account_a));
+  EXPECT_TRUE(context()->HasPermission(relying_party_requester,
+                                       relying_party_embedder,
+                                       identity_provider, account_b));
+  EXPECT_FALSE(context()->HasPermission(relying_party_requester,
+                                        relying_party_embedder,
+                                        identity_provider, account_c));
+  EXPECT_TRUE(
+      context()->HasPermission(relying_party_requester, relying_party_embedder,
+                               identity_provider, /*account_id=*/std::nullopt));
+
+  // RefreshExistingPermission works with an old account but does not work if
+  // account does not exist.
+  EXPECT_TRUE(context()->RefreshExistingPermission(
+      relying_party_requester, relying_party_embedder, identity_provider,
+      account_b));
+  EXPECT_FALSE(context()->RefreshExistingPermission(
+      relying_party_requester, relying_party_embedder, identity_provider,
+      account_c));
+
+  // Add a third account, with timestamp.
+  context()->GrantPermission(relying_party_requester, relying_party_embedder,
+                             identity_provider, account_c);
+
+  EXPECT_TRUE(context()->HasPermission(relying_party_requester,
+                                       relying_party_embedder,
+                                       identity_provider, account_a));
+  EXPECT_TRUE(context()->HasPermission(relying_party_requester,
+                                       relying_party_embedder,
+                                       identity_provider, account_b));
+  EXPECT_TRUE(context()->HasPermission(relying_party_requester,
+                                       relying_party_embedder,
+                                       identity_provider, account_c));
+
+  // RefreshExistingPermission works with the new format.
+  EXPECT_TRUE(context()->RefreshExistingPermission(
+      relying_party_requester, relying_party_embedder, identity_provider,
+      account_c));
+
+  // GetAllDataKeys() does not crash.
+  context()->GetAllDataKeys(base::DoNothing());
+
+  // Revoke works with both formats.
+  base::test::TestFuture<void> future;
+  context()->RevokePermission(relying_party_requester, relying_party_embedder,
+                              identity_provider, account_a,
+                              future.GetCallback());
+  ASSERT_TRUE(future.Wait());
+  // Revoke works with both formats.
+  base::test::TestFuture<void> future2;
+  context()->RevokePermission(relying_party_requester, relying_party_embedder,
+                              identity_provider, account_c,
+                              future2.GetCallback());
+  ASSERT_TRUE(future2.Wait());
+
+  EXPECT_FALSE(context()->HasPermission(relying_party_requester,
+                                        relying_party_embedder,
+                                        identity_provider, account_a));
+  EXPECT_TRUE(context()->HasPermission(relying_party_requester,
+                                       relying_party_embedder,
+                                       identity_provider, account_b));
+  EXPECT_FALSE(context()->HasPermission(relying_party_requester,
+                                        relying_party_embedder,
+                                        identity_provider, account_c));
+  EXPECT_TRUE(
+      context()->HasPermission(relying_party_requester, relying_party_embedder,
+                               identity_provider, /*account_id=*/std::nullopt));
+}
diff --git a/chrome/browser/webid/federated_identity_permission_context.cc b/chrome/browser/webid/federated_identity_permission_context.cc
index 6bd5bb4..177fbd9b 100644
--- a/chrome/browser/webid/federated_identity_permission_context.cc
+++ b/chrome/browser/webid/federated_identity_permission_context.cc
@@ -93,6 +93,16 @@
                                      account_id, base::DoNothing());
 }
 
+void FederatedIdentityPermissionContext::RefreshExistingSharingPermission(
+    const url::Origin& relying_party_requester,
+    const url::Origin& relying_party_embedder,
+    const url::Origin& identity_provider,
+    const std::string& account_id) {
+  sharing_context_->RefreshExistingPermission(relying_party_requester,
+                                              relying_party_embedder,
+                                              identity_provider, account_id);
+}
+
 ContentSettingsForOneType FederatedIdentityPermissionContext::
     GetSharingPermissionGrantsAsContentSettings() {
   return sharing_context_->GetSharingPermissionGrantsAsContentSettings();
diff --git a/chrome/browser/webid/federated_identity_permission_context.h b/chrome/browser/webid/federated_identity_permission_context.h
index 367f57f0..ad5e9ee 100644
--- a/chrome/browser/webid/federated_identity_permission_context.h
+++ b/chrome/browser/webid/federated_identity_permission_context.h
@@ -64,6 +64,11 @@
                                const url::Origin& relying_party_embedder,
                                const url::Origin& identity_provider,
                                const std::string& account_id) override;
+  void RefreshExistingSharingPermission(
+      const url::Origin& relying_party_requester,
+      const url::Origin& relying_party_embedder,
+      const url::Origin& identity_provider,
+      const std::string& account_id) override;
   std::optional<bool> GetIdpSigninStatus(
       const url::Origin& idp_origin) override;
   void SetIdpSigninStatus(const url::Origin& idp_origin,
diff --git a/chrome/browser/win/conflicts/incompatible_applications_browsertest.cc b/chrome/browser/win/conflicts/incompatible_applications_browsertest.cc
index 8f0472c..fb81a74 100644
--- a/chrome/browser/win/conflicts/incompatible_applications_browsertest.cc
+++ b/chrome/browser/win/conflicts/incompatible_applications_browsertest.cc
@@ -15,9 +15,11 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_reg_util_win.h"
 #include "base/threading/thread_restrictions.h"
+#include "base/win/registry.h"
 #include "base/win/win_util.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/win/conflicts/incompatible_applications_updater.h"
+#include "chrome/browser/win/conflicts/installed_applications.h"
 #include "chrome/browser/win/conflicts/module_database.h"
 #include "chrome/browser/win/conflicts/proto/module_list.pb.h"
 #include "chrome/browser/win/conflicts/third_party_conflicts_manager.h"
@@ -78,6 +80,20 @@
   base::RepeatingClosure run_loop_quit_closure_;
 };
 
+class TestInstalledApplications : public InstalledApplications {
+ public:
+  // For browser_tests, only search in HKCU, because read/write HKLM
+  // may:
+  //  1. get interfered by the test bot.
+  //  2. break sandbox operation and things fail unpredictably.
+  std::vector<std::pair<HKEY, REGSAM>> GenRegistryKeyCombinations()
+      const override {
+    std::vector<std::pair<HKEY, REGSAM>> registry_key_combinations;
+    registry_key_combinations.emplace_back(HKEY_CURRENT_USER, 0);
+    return registry_key_combinations;
+  }
+};
+
 class IncompatibleApplicationsBrowserTest : public InProcessBrowserTest {
  public:
   IncompatibleApplicationsBrowserTest(
@@ -97,8 +113,6 @@
     ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
 
     ASSERT_NO_FATAL_FAILURE(
-        registry_override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE));
-    ASSERT_NO_FATAL_FAILURE(
         registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
 
     scoped_feature_list_.InitAndEnableFeature(
@@ -208,7 +222,8 @@
       FROM_HERE,
       base::BindLambdaForTesting([module_list_path = GetModuleListPath(),
                                   quit_closure = run_loop.QuitClosure()]() {
-        ModuleDatabase* module_database = ModuleDatabase::GetInstance();
+        ModuleDatabase* module_database = ModuleDatabase::GetInstanceForTesting(
+            std::make_unique<TestInstalledApplications>());
 
         // Simulate the download of the module list component.
         module_database->third_party_conflicts_manager()->LoadModuleList(
diff --git a/chrome/browser/win/conflicts/installed_applications.cc b/chrome/browser/win/conflicts/installed_applications.cc
index c9936a68..34396cf9 100644
--- a/chrome/browser/win/conflicts/installed_applications.cc
+++ b/chrome/browser/win/conflicts/installed_applications.cc
@@ -123,6 +123,29 @@
   static constexpr wchar_t kUninstallKeyPath[] =
       L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
 
+  // Retrieve the current user's Security Identifier. If it fails, |user_sid|
+  // will stay empty.
+  std::wstring user_sid;
+  base::win::GetUserSidString(&user_sid);
+
+  for (const auto& combination : GenRegistryKeyCombinations()) {
+    for (base::win::RegistryKeyIterator i(combination.first, kUninstallKeyPath,
+                                          combination.second);
+         i.Valid(); ++i) {
+      CheckRegistryKeyForInstalledApplication(
+          combination.first, kUninstallKeyPath, combination.second, i.Name(),
+          *msi_util, user_sid);
+    }
+  }
+
+  // The vectors are sorted so that binary searching can be used. No additional
+  // entries will be added anyways.
+  SortByFilePaths(&installed_files_);
+  SortByFilePaths(&install_directories_);
+}
+
+std::vector<std::pair<HKEY, REGSAM>>
+InstalledApplications::GenRegistryKeyCombinations() const {
   std::vector<std::pair<HKEY, REGSAM>> registry_key_combinations;
   if (base::win::OSInfo::GetArchitecture() ==
       base::win::OSInfo::X86_ARCHITECTURE) {
@@ -138,26 +161,7 @@
     registry_key_combinations.emplace_back(HKEY_LOCAL_MACHINE, KEY_WOW64_32KEY);
     registry_key_combinations.emplace_back(HKEY_LOCAL_MACHINE, KEY_WOW64_64KEY);
   }
-
-  // Retrieve the current user's Security Identifier. If it fails, |user_sid|
-  // will stay empty.
-  std::wstring user_sid;
-  base::win::GetUserSidString(&user_sid);
-
-  for (const auto& combination : registry_key_combinations) {
-    for (base::win::RegistryKeyIterator i(combination.first, kUninstallKeyPath,
-                                          combination.second);
-         i.Valid(); ++i) {
-      CheckRegistryKeyForInstalledApplication(
-          combination.first, kUninstallKeyPath, combination.second, i.Name(),
-          *msi_util, user_sid);
-    }
-  }
-
-  // The vectors are sorted so that binary searching can be used. No additional
-  // entries will be added anyways.
-  SortByFilePaths(&installed_files_);
-  SortByFilePaths(&install_directories_);
+  return registry_key_combinations;
 }
 
 void InstalledApplications::CheckRegistryKeyForInstalledApplication(
diff --git a/chrome/browser/win/conflicts/installed_applications.h b/chrome/browser/win/conflicts/installed_applications.h
index 88d01067..27c7a6e 100644
--- a/chrome/browser/win/conflicts/installed_applications.h
+++ b/chrome/browser/win/conflicts/installed_applications.h
@@ -12,6 +12,7 @@
 
 #include "base/files/file_path.h"
 #include "base/gtest_prod_util.h"
+#include "base/win/registry.h"
 #include "base/win/windows_types.h"
 
 class MsiUtil;
@@ -69,6 +70,9 @@
 
   virtual ~InstalledApplications();
 
+  virtual std::vector<std::pair<HKEY, REGSAM>> GenRegistryKeyCombinations()
+      const;
+
   // Given a |file|, checks if it matches an installed application on the user's
   // machine and appends all the matching applications to |applications|.
   // Virtual to allow mocking.
diff --git a/chrome/browser/win/conflicts/module_database.cc b/chrome/browser/win/conflicts/module_database.cc
index 35a5e078..25400be 100644
--- a/chrome/browser/win/conflicts/module_database.cc
+++ b/chrome/browser/win/conflicts/module_database.cc
@@ -298,6 +298,16 @@
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 // static
+ModuleDatabase* ModuleDatabase::GetInstanceForTesting(
+    std::unique_ptr<InstalledApplications> installed_applications) {
+  CHECK(g_module_database->third_party_conflicts_manager_);
+  g_module_database->third_party_conflicts_manager_
+      ->SetInstalledApplicationsForTesting(  // IN-TEST
+          std::move(installed_applications));
+  return GetInstance();
+}
+
+// static
 void ModuleDatabase::RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
   // Register the pref used to disable the Incompatible Applications warning and
   // the blocking of third-party modules using group policy. Enabled by default.
diff --git a/chrome/browser/win/conflicts/module_database.h b/chrome/browser/win/conflicts/module_database.h
index eed5e702..ade0964a 100644
--- a/chrome/browser/win/conflicts/module_database.h
+++ b/chrome/browser/win/conflicts/module_database.h
@@ -14,6 +14,7 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "build/branding_buildflags.h"
+#include "chrome/browser/win/conflicts/installed_applications.h"
 #include "chrome/browser/win/conflicts/module_info.h"
 #include "chrome/browser/win/conflicts/module_inspector.h"
 #include "chrome/browser/win/conflicts/third_party_metrics_recorder.h"
@@ -148,6 +149,11 @@
   void StartInspection();
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  // Similar with the GetInstance() but overwriting third party conflicts
+  // manager's installed_applications_ for testing.
+  static ModuleDatabase* GetInstanceForTesting(
+      std::unique_ptr<InstalledApplications>);
+
   static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
 
   // Returns false if third-party modules blocking is disabled via
diff --git a/chrome/browser/win/conflicts/third_party_conflicts_manager.h b/chrome/browser/win/conflicts/third_party_conflicts_manager.h
index 2de1517f..6eb90a9 100644
--- a/chrome/browser/win/conflicts/third_party_conflicts_manager.h
+++ b/chrome/browser/win/conflicts/third_party_conflicts_manager.h
@@ -15,6 +15,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/win/conflicts/installed_applications.h"
 #include "chrome/browser/win/conflicts/module_blocklist_cache_updater.h"
 #include "chrome/browser/win/conflicts/module_database_observer.h"
 #include "chrome/browser/win/conflicts/module_list_component_updater.h"
@@ -101,6 +102,11 @@
   // Loads the |module_list_filter_| using the Module List at |path|.
   void LoadModuleList(const base::FilePath& path);
 
+  void SetInstalledApplicationsForTesting(
+      std::unique_ptr<InstalledApplications> installed_applications) {
+    installed_applications_ = std::move(installed_applications);
+  }
+
   // Force the initialization of the IncompatibleApplicationsUpdater and the
   // ModuleBlocklistCacheUpdater instances by triggering an update of the module
   // list component, if needed. Immediately invokes
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index cb9ba31..f680c1a 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1712750164-f28466362e9566e866576bb2452959bf6788514b-5383250954e0f9e2856fd7c73becb0281c48339c.profdata
+chrome-android32-main-1712793593-cae4312ebe70db70f6fca21a4ec89014b47d3a0e-25ba3307b92317024b267f58a62418fb6ca41e8a.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index b4bea2f..4d18475 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1712764607-bc0aa86f8ae7eab9bbda4681d62e7731e29b7073-4deac488e8549ac9aea5647ac37b40649f95a534.profdata
+chrome-android64-main-1712779168-f3aed91a650e73b32c48eb6fe5f7d160154ef49f-70851099c78cdb9e06f8342fa6cf601d695d013d.profdata
diff --git a/chrome/build/lacros64.pgo.txt b/chrome/build/lacros64.pgo.txt
index f45110dd..1cc2296b 100644
--- a/chrome/build/lacros64.pgo.txt
+++ b/chrome/build/lacros64.pgo.txt
@@ -1 +1 @@
-chrome-chromeos-amd64-generic-main-1712750164-127570232e19aa992bd5fb402b73d0cf8c618a9e-5383250954e0f9e2856fd7c73becb0281c48339c.profdata
+chrome-chromeos-amd64-generic-main-1712794122-27081128158b4d2a69367a5472c1fc88fd67bbf4-8f54dd616c9031b77d46b5f8666213b67224efe6.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index a8cf0229c..9319e183 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1712728706-0a45f287f4031f5fe8e97231a1b40958861d238c-20025605e6812fb0e48466f585660bff8502c1f7.profdata
+chrome-linux-main-1712793593-0ba967a28b8dcac8770bbabddb038eac1cac9eca-25ba3307b92317024b267f58a62418fb6ca41e8a.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 5897cee..e2d3b74 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1712750164-43d27558b198735e9cbf8501cde460b5c0b53308-5383250954e0f9e2856fd7c73becb0281c48339c.profdata
+chrome-mac-arm-main-1712807836-f6aa73235f0783aa3cf386b9fd46ade573c05dac-0d379889bb749cb52bdb96a980d32cb4d4191e0f.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index ec401e5..ee7395f 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1712750164-98e3f439e25de28e41b4fb9753237cf8909a9946-5383250954e0f9e2856fd7c73becb0281c48339c.profdata
+chrome-mac-main-1712793593-0ae8745fd437688f70506e78e56c977e5bff1379-25ba3307b92317024b267f58a62418fb6ca41e8a.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index b43559d..eec67a37 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1712750164-a2edd83a96d70a9285da5903a4d00d894c22375c-5383250954e0f9e2856fd7c73becb0281c48339c.profdata
+chrome-win-arm64-main-1712793593-3f36a4e2a9c0646e8878aec063cc3aa5c3ff9fbf-25ba3307b92317024b267f58a62418fb6ca41e8a.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 32d7808..abe942e 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1712750164-5dafd85023b4fb0c864288e6a9f2ba1bea284c65-5383250954e0f9e2856fd7c73becb0281c48339c.profdata
+chrome-win32-main-1712793593-2b0a15c5023bd513fd76d858c4221945c4696080-25ba3307b92317024b267f58a62418fb6ca41e8a.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index ce8815c..4bf9e79 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1712750164-1c8d28e344927d34aa29a4c7018770af25691416-5383250954e0f9e2856fd7c73becb0281c48339c.profdata
+chrome-win64-main-1712782756-7d62838f9169aa3b8e50f4ed0eccbf8c67d51dd1-953fc520cbcd7ba90b383a4e0e8c11ebc9d399b1.profdata
diff --git a/chrome/common/chromeos/extensions/api/diagnostics.idl b/chrome/common/chromeos/extensions/api/diagnostics.idl
index cc7f3fca..f39ae776 100644
--- a/chrome/common/chromeos/extensions/api/diagnostics.idl
+++ b/chrome/common/chromeos/extensions/api/diagnostics.idl
@@ -272,6 +272,11 @@
   };
 
   dictionary MemtesterResult {
+    MemtesterTestItemEnum[] passedItems;
+    MemtesterTestItemEnum[] failedItems;
+  };
+
+  dictionary LegacyMemtesterResult {
     MemtesterTestItemEnum[] passed_items;
     MemtesterTestItemEnum[] failed_items;
   };
@@ -289,7 +294,7 @@
     // Number of bytes tested in the memory routine.
     double? bytesTested;
     // Contains the memtester test results.
-    MemtesterResult? result;
+    LegacyMemtesterResult? result;
   };
 
   dictionary CreateMemoryRoutineArguments {
@@ -312,6 +317,14 @@
 
   dictionary CreateVolumeButtonRoutineArguments {
     // The volume button to be tested.
+    VolumeButtonType buttonType;
+    // Length of time to listen to the volume button events. The value should be
+    // positive and less or equal to 600 seconds.
+    long timeoutSeconds;
+  };
+
+  dictionary LegacyCreateVolumeButtonRoutineArguments {
+    // The volume button to be tested.
     VolumeButtonType button_type;
     // Length of time to listen to the volume button events. The value should be
     // positive and less or equal to 600 seconds.
@@ -329,11 +342,11 @@
 
   dictionary FanRoutineFinishedDetail {
     // The ids of fans that can be controlled.
-    long[]? passed_fan_ids;
+    long[]? passedFanIds;
     // The ids of fans that cannot be controlled.
-    long[]? failed_fan_ids;
+    long[]? failedFanIds;
     // Whether the number of fan probed is matched.
-    HardwarePresenceStatus? fan_count_status;
+    HardwarePresenceStatus? fanCountStatus;
   };
 
   dictionary LegacyFanRoutineFinishedInfo {
@@ -365,7 +378,7 @@
 
   dictionary RoutineFinishedInfo {
     DOMString? uuid;
-    boolean? has_passed;
+    boolean? hasPassed;
     RoutineFinishedDetailUnion? detail;
   };
 
@@ -542,14 +555,14 @@
 
     // Deprecated. Use `createRoutine` instead.
     static void createVolumeButtonRoutine(
-        CreateVolumeButtonRoutineArguments args,
+        LegacyCreateVolumeButtonRoutineArguments args,
         CreateRoutineCallback callback);
 
     // Deprecated. Use `isRoutineArgumentSupported` instead.
-    // Checks whether a certain `CreateVolumeButtonRoutineArguments` is
+    // Checks whether a certain `LegacyCreateVolumeButtonRoutineArguments` is
     // supported on the platform.
     static void isVolumeButtonRoutineArgumentSupported(
-        CreateVolumeButtonRoutineArguments args,
+        LegacyCreateVolumeButtonRoutineArguments args,
         RoutineSupportStatusInfoCallback callback);
 
     // Deprecated. Use `createRoutine` instead.
diff --git a/chrome/services/util_win/public/mojom/util_win_mojom_traits.cc b/chrome/services/util_win/public/mojom/util_win_mojom_traits.cc
index e42a025..18059e5 100644
--- a/chrome/services/util_win/public/mojom/util_win_mojom_traits.cc
+++ b/chrome/services/util_win/public/mojom/util_win_mojom_traits.cc
@@ -100,7 +100,7 @@
     case base::win::ShortcutOperation::kUpdateExisting:
       return chrome::mojom::ShortcutOperation::kUpdateExisting;
   }
-  NOTREACHED();
+  DUMP_WILL_BE_NOTREACHED_NORETURN();
   return chrome::mojom::ShortcutOperation::kCreateAlways;
 }
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 0229262..d22b695 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3095,6 +3095,7 @@
       deps += [
         "//ash/webui/system_apps/public:system_web_app_type",
         "//chrome/browser/apps/app_service/app_install",
+        "//chrome/browser/apps/app_service/app_install:proto",
         "//chrome/browser/ash/system_web_apps:browser_tests",
         "//chrome/browser/ash/system_web_apps/test_support",
         "//chrome/browser/ash/system_web_apps/test_support:test_support_ui",
@@ -8682,7 +8683,6 @@
       "//chrome/browser/nearby_sharing/logging:unit_tests",
       "//chrome/browser/nearby_sharing/proto:tachyon_proto",
       "//chrome/browser/nearby_sharing/public/cpp",
-      "//chrome/browser/nearby_sharing/public/cpp:test_support",
       "//chrome/browser/policy:onc",
       "//chrome/browser/policy:unit_tests",
       "//chrome/browser/push_notification:push_notification",
@@ -8732,6 +8732,7 @@
       "//chromeos/ash/components/login/login_state:test_support",
       "//chromeos/ash/components/login/session",
       "//chromeos/ash/components/multidevice:test_support",
+      "//chromeos/ash/components/nearby/common/connections_manager:test_support",
       "//chromeos/ash/components/proximity_auth",
       "//chromeos/ash/components/proximity_auth:test_support",
       "//chromeos/ash/components/standalone_browser:standalone_browser",
@@ -9132,6 +9133,7 @@
       "../browser/extensions/updater/extension_updater_switches_unittest.cc",
       "../browser/extensions/updater/extension_updater_unittest.cc",
       "../browser/extensions/user_script_listener_unittest.cc",
+      "../browser/extensions/user_script_world_configuration_manager_unittest.cc",
       "../browser/extensions/warning_badge_service_unittest.cc",
       "../browser/extensions/webstore_installer_unittest.cc",
       "../browser/extensions/zipfile_installer_unittest.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/testhtmls/PopupOnClickPageStation.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/testhtmls/PopupOnClickPageStation.java
index 48e32cc5..35f5cb8 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/testhtmls/PopupOnClickPageStation.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/testhtmls/PopupOnClickPageStation.java
@@ -6,6 +6,7 @@
 
 import org.chromium.base.test.transit.Elements;
 import org.chromium.base.test.transit.StationFacility;
+import org.chromium.base.test.transit.Transition;
 import org.chromium.base.test.transit.Trip;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.transit.PageStation;
@@ -53,7 +54,7 @@
                         .withIsOpeningTab(true)
                         .withIsSelectingTab(true)
                         .build();
-        return Trip.travelSync(this, newPage, mLinkToPopup::click);
+        return Trip.travelSync(this, newPage, Transition.retryOption(), mLinkToPopup::click);
     }
 
     /**
@@ -62,6 +63,6 @@
      */
     public PopupBlockedMessageFacility clickLinkAndExpectPopupBlockedMessage() {
         PopupBlockedMessageFacility infoBar = new PopupBlockedMessageFacility(this, 1);
-        return StationFacility.enterSync(infoBar, mLinkToPopup::click);
+        return StationFacility.enterSync(infoBar, Transition.retryOption(), mLinkToPopup::click);
     }
 }
diff --git a/chrome/test/base/android/android_browser_test_browsertest_android.cc b/chrome/test/base/android/android_browser_test_browsertest_android.cc
index 55928fb..b871191 100644
--- a/chrome/test/base/android/android_browser_test_browsertest_android.cc
+++ b/chrome/test/base/android/android_browser_test_browsertest_android.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/flags/android/chrome_feature_list.h"
 #include "chrome/browser/ui/android/tab_model/tab_model.h"
 #include "chrome/browser/ui/android/tab_model/tab_model_list.h"
 #include "chrome/test/base/android/android_browser_test.h"
@@ -9,7 +10,11 @@
 #include "content/public/test/browser_test_utils.h"
 
 IN_PROC_BROWSER_TEST_F(AndroidBrowserTest, Smoke) {
-  ASSERT_EQ(TabModelList::models().size(), 1u);
+  if (base::FeatureList::IsEnabled(chrome::android::kAndroidTabDeclutter)) {
+    ASSERT_EQ(TabModelList::models().size(), 2u);
+  } else {
+    ASSERT_EQ(TabModelList::models().size(), 1u);
+  }
 
   // Grab a tab an navigate its contents
   const TabModel* tab_model = TabModelList::models()[0];
diff --git a/chrome/test/base/chromeos/crosier/gaia_host_util.cc b/chrome/test/base/chromeos/crosier/gaia_host_util.cc
index e191c340..c8d06e9 100644
--- a/chrome/test/base/chromeos/crosier/gaia_host_util.cc
+++ b/chrome/test/base/chromeos/crosier/gaia_host_util.cc
@@ -37,7 +37,8 @@
   // Wait for Gaia page to be ready and update properties..
   const std::string check_gaia_js = R"((function() {
     gaiaSignin = $('gaia-signin');
-    return !gaiaSignin.hidden && gaiaSignin.uiStep === 'online-gaia';
+    return !gaiaSignin.hidden && gaiaSignin.uiStep === 'online-gaia' &&
+        !gaiaSignin.loadingFrameContents && gaiaSignin.showViewProcessed;
   })())";
   ash::test::OobeJS().CreateWaiter(check_gaia_js)->Wait();
 }
diff --git a/chrome/test/data/extensions/api_test/file_system_provider/notify/test.js b/chrome/test/data/extensions/api_test/file_system_provider/notify/test.js
index 6797c82..3816b74 100644
--- a/chrome/test/data/extensions/api_test/file_system_provider/notify/test.js
+++ b/chrome/test/data/extensions/api_test/file_system_provider/notify/test.js
@@ -19,13 +19,19 @@
  * @type {string}
  * @const
  */
-var TESTING_TAG = "hello-puppy";
+var TESTING_TAG1 = 'hello-puppy';
 
 /**
  * @type {string}
  * @const
  */
-var TESTING_ANOTHER_TAG = "hello-giraffe";
+var TESTING_TAG2 = 'hello-cat';
+
+/**
+ * @type {string}
+ * @const
+ */
+var TESTING_TAG3 = 'hello-giraffe';
 
 /**
  * List of directory changed events received from the chrome.fileManagerPrivate
@@ -91,7 +97,7 @@
                       externalEntry,
                       chrome.test.callbackPass(function(result) {
                         chrome.test.assertTrue(result);
-                        // Verify closure called when an even arrives.
+                        // Verify closure called when an event arrives.
                         directoryChangedCallback = chrome.test.callbackPass(
                             function() {
                               chrome.test.assertEq(
@@ -110,18 +116,27 @@
                                         1, items[0].watchers.length);
                                     var watcher = items[0].watchers[0];
                                     chrome.test.assertEq(
-                                        TESTING_TAG, watcher.lastTag);
+                                        TESTING_TAG1, watcher.lastTag);
                                   }));
                             });
                         // TODO(mtomasz): Add more advanced tests, eg. for the
                         // details of changes.
-                        chrome.fileSystemProvider.notify({
-                          fileSystemId: test_util.FILE_SYSTEM_ID,
-                          observedPath: fileEntry.fullPath,
-                          recursive: false,
-                          changeType: 'CHANGED',
-                          tag: TESTING_TAG
-                        }, chrome.test.callbackPass());
+                        chrome.fileSystemProvider.notify(
+                            {
+                              fileSystemId: test_util.FILE_SYSTEM_ID,
+                              observedPath: fileEntry.fullPath,
+                              recursive: false,
+                              changeType: 'CHANGED',
+                              changes: [{
+                                entryPath: fileEntry.fullPath,
+                                changeType: 'CHANGED',
+                                cloudFileInfo: {
+                                  versionTag: 'abc',
+                                }
+                              }],
+                              tag: TESTING_TAG1
+                            },
+                            chrome.test.callbackPass());
                       }));
                 })).catch(chrome.test.fail);
           }), function(error) {
@@ -129,6 +144,30 @@
           });
     },
 
+    // Notifying with a null cloudFileInfo should succeed.
+    function notifyEmptyCloudFileInfo() {
+      test_util.fileSystem.root.getDirectory(
+          TESTING_DIRECTORY.name, {create: false},
+          chrome.test.callbackPass(function(fileEntry) {
+            chrome.test.assertEq(TESTING_DIRECTORY.name, fileEntry.name);
+            directoryChangedCallback = function() {};
+            chrome.fileSystemProvider.notify(
+                {
+                  fileSystemId: test_util.FILE_SYSTEM_ID,
+                  observedPath: fileEntry.fullPath,
+                  recursive: false,
+                  changeType: 'CHANGED',
+                  // No cloudFileInfo.
+                  changes: [{
+                    entryPath: fileEntry.fullPath,
+                    changeType: 'CHANGED',
+                  }],
+                  tag: TESTING_TAG2,
+                },
+                chrome.test.callbackPass());
+          }));
+    },
+
     // Passing an empty tag (or no tag) is invalid when the file system supports
     // the tag.
     function notifyEmptyTag() {
@@ -165,13 +204,15 @@
               chrome.test.fail();
             };
             // TODO(mtomasz): NOT_FOUND error should be returned instead.
-            chrome.fileSystemProvider.notify({
-              fileSystemId: test_util.FILE_SYSTEM_ID,
-              observedPath: fileEntry.fullPath,
-              recursive: true,
-              changeType: 'CHANGED',
-              tag: TESTING_ANOTHER_TAG,
-            }, chrome.test.callbackFail('NOT_FOUND'));
+            chrome.fileSystemProvider.notify(
+                {
+                  fileSystemId: test_util.FILE_SYSTEM_ID,
+                  observedPath: fileEntry.fullPath,
+                  recursive: true,
+                  changeType: 'CHANGED',
+                  tag: TESTING_TAG3,
+                },
+                chrome.test.callbackFail('NOT_FOUND'));
           }));
     },
 
@@ -187,9 +228,9 @@
             test_util.toExternalEntry(fileEntry).then(
                 chrome.test.callbackPass(function(externalEntry) {
                   chrome.test.assertTrue(!!externalEntry);
-                  directoryChangedCallback = chrome.test.callbackPass(
-                      function() {
-                        chrome.test.assertEq(2, directoryChangedEvents.length);
+                  directoryChangedCallback =
+                      chrome.test.callbackPass(function() {
+                        chrome.test.assertEq(3, directoryChangedEvents.length);
                         chrome.test.assertEq(
                             'changed', directoryChangedEvents[1].eventType);
                         chrome.test.assertEq(
@@ -205,13 +246,15 @@
                       });
                   // TODO(mtomasz): Add more advanced tests, eg. for the details
                   // of changes.
-                  chrome.fileSystemProvider.notify({
-                    fileSystemId: test_util.FILE_SYSTEM_ID,
-                    observedPath: fileEntry.fullPath,
-                    recursive: false,
-                    changeType: 'DELETED',
-                    tag: TESTING_ANOTHER_TAG
-                  }, chrome.test.callbackPass());
+                  chrome.fileSystemProvider.notify(
+                      {
+                        fileSystemId: test_util.FILE_SYSTEM_ID,
+                        observedPath: fileEntry.fullPath,
+                        recursive: false,
+                        changeType: 'DELETED',
+                        tag: TESTING_TAG3
+                      },
+                      chrome.test.callbackPass());
                 })).catch(chrome.test.fail);
           }));
     },
@@ -231,13 +274,15 @@
                     chrome.test.fail();
                   };
                   // TODO(mtomasz): NOT_FOUND error should be returned instead.
-                  chrome.fileSystemProvider.notify({
-                    fileSystemId: test_util.FILE_SYSTEM_ID,
-                    observedPath: fileEntry.fullPath,
-                    recursive: false,
-                    changeType: 'CHANGED',
-                    tag: TESTING_ANOTHER_TAG
-                  }, chrome.test.callbackFail('NOT_FOUND'));
+                  chrome.fileSystemProvider.notify(
+                      {
+                        fileSystemId: test_util.FILE_SYSTEM_ID,
+                        observedPath: fileEntry.fullPath,
+                        recursive: false,
+                        changeType: 'CHANGED',
+                        tag: TESTING_TAG3
+                      },
+                      chrome.test.callbackFail('NOT_FOUND'));
                 })).catch(chrome.test.fail);
             }));
     }
diff --git a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_test.ts b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_test.ts
index 65e8f935..92a58745 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_test.ts
@@ -636,7 +636,7 @@
         'wait for close button to load');
     closeIntroductionButton.click();
 
-    const seaPenTemplateQuery = await getSeaPenTemplateQuery(5);
+    const seaPenTemplateQuery = await getSeaPenTemplateQuery(6);
     assertTrue(!!seaPenTemplateQuery, 'Characters template should show up');
 
     const seaPenChips = await waitUntil(
@@ -712,7 +712,7 @@
 
       const expectedWallpaperTitle =
           seaPenTemplateQuery.shadowRoot?.getElementById('template')
-              ?.textContent?.replace(/\s+/gmi, ' ')
+              ?.textContent?.replace(/\s+/gmi, '')
               .trim();
 
       // Goes back to sea pen root page.
@@ -731,7 +731,9 @@
         assertTrue(!!textContainer, 'wallpaper text container exists');
         assertEquals(
             expectedWallpaperTitle,
-            textContainer?.querySelector('#imageTitle')?.textContent?.trim(),
+            textContainer?.querySelector('#imageTitle')
+                ?.textContent?.replace(/\s+/gmi, '')
+                .trim(),
             'image title is correct');
       }
 
@@ -767,7 +769,9 @@
             recentImages.shadowRoot?.querySelector<HTMLParagraphElement>(
                 'p.about-prompt-info');
         assertTrue(
-            !!promptInfo?.textContent?.trim().includes(expectedWallpaperTitle!),
+            !!promptInfo?.textContent?.replace(/\s+/gmi, '')
+                  .trim()
+                  .includes(expectedWallpaperTitle!),
             `prompt info should include ${expectedWallpaperTitle}`);
       }
     });
@@ -842,7 +846,7 @@
 
   test('delete recent image', async () => {
     const seaPenRouter = await getSeaPenRouter();
-    const recentImages = await waitUntil(
+    let recentImages = await waitUntil(
         () => seaPenRouter.shadowRoot
                   ?.querySelector<SeaPenRecentWallpapersElement>(
                       'sea-pen-recent-wallpapers'),
@@ -868,14 +872,15 @@
     assertTrue(!!deleteButton, 'delete wallpaper button exists');
     deleteButton!.click();
 
-    images = await waitUntil(
-        () =>
-            recentImages.shadowRoot?.querySelectorAll<WallpaperGridItemElement>(
-                `.recent-image-container:not([hidden])`),
-        'waiting for recent images');
+    recentImages = await waitUntil(
+        () => seaPenRouter.shadowRoot
+                  ?.querySelector<SeaPenRecentWallpapersElement>(
+                      'sea-pen-recent-wallpapers'),
+        'waiting for sea-pen-recent-wallpapers');
+    images = images = recentImages.shadowRoot?.querySelectorAll<HTMLElement>(
+        `.recent-image-container:not([hidden])`);
     assertTrue(!!images, 'images should still exist');
-    assertEquals(
-        numImages - 1, images.length, 'a recent image has been deleted');
+    assertGT(numImages, images.length, 'a recent image has been deleted');
   });
 });
 
diff --git a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_router_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_router_element_test.ts
index 3e861eb..3f6bb93 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_router_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_router_element_test.ts
@@ -131,7 +131,7 @@
             routerElement.shadowRoot?.querySelector('iron-location')?.path,
             'navigates to result page');
         assertEquals(
-            'seaPenTemplateId=10',
+            'seaPenTemplateId=4',
             routerElement.shadowRoot?.querySelector('iron-location')?.query,
             'query as selected template id');
 
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/BUILD.gn b/chrome/test/data/webui/chromeos/shimless_rma/BUILD.gn
index b12c5f3..d4bf5ce 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/BUILD.gn
+++ b/chrome/test/data/webui/chromeos/shimless_rma/BUILD.gn
@@ -29,7 +29,7 @@
     "onboarding_update_page_test.ts",
     "onboarding_wait_for_manual_wp_disable_page_test.ts",
     "onboarding_wp_disable_complete_page_test.ts",
-    "reboot_page_test.js",
+    "reboot_page_test.ts",
     "reimaging_calibration_failed_page_test.js",
     "reimaging_calibration_run_page_test.js",
     "reimaging_calibration_setup_page_test.js",
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/reboot_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/reboot_page_test.js
deleted file mode 100644
index 1b96bfc7..0000000
--- a/chrome/test/data/webui/chromeos/shimless_rma/reboot_page_test.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {PromiseResolver} from 'chrome://resources/ash/common/promise_resolver.js';
-import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js';
-import {setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js';
-import {RebootPage} from 'chrome://shimless-rma/reboot_page.js';
-import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
-
-import {assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
-
-suite('rebootPageTest', function() {
-  /** @type {?RebootPage} */
-  let component = null;
-
-  /** @type {?FakeShimlessRmaService} */
-  let service = null;
-
-  setup(() => {
-    document.body.innerHTML = trustedTypes.emptyHTML;
-    service = new FakeShimlessRmaService();
-    setShimlessRmaServiceForTesting(service);
-  });
-
-  teardown(() => {
-    component.remove();
-    component = null;
-    service.reset();
-  });
-
-  /**
-   * @return {!Promise}
-   */
-  function initializeRebootPage() {
-    assertFalse(!!component);
-
-    component =
-        /** @type {!RebootPage} */ (document.createElement('reboot-page'));
-    assertTrue(!!component);
-    document.body.appendChild(component);
-
-    return flushTasks();
-  }
-
-  test('ComponentRenders', async () => {
-    await initializeRebootPage();
-    const basePage = component.shadowRoot.querySelector('base-page');
-    assertTrue(!!basePage);
-  });
-});
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/reboot_page_test.ts b/chrome/test/data/webui/chromeos/shimless_rma/reboot_page_test.ts
new file mode 100644
index 0000000..af77b1e
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/shimless_rma/reboot_page_test.ts
@@ -0,0 +1,77 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://shimless-rma/shimless_rma.js';
+
+import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
+import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
+import {assert} from 'chrome://resources/js/assert.js';
+import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js';
+import {setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js';
+import {RebootPage} from 'chrome://shimless-rma/reboot_page.js';
+import {RmadErrorCode} from 'chrome://shimless-rma/shimless_rma.mojom-webui.js';
+import {assertEquals} from 'chrome://webui-test/chromeos/chai_assert.js';
+import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
+
+suite('rebootPageTest', function() {
+  let component: RebootPage|null = null;
+
+  const service: FakeShimlessRmaService = new FakeShimlessRmaService();
+
+  setup(() => {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    setShimlessRmaServiceForTesting(service);
+  });
+
+  teardown(() => {
+    component?.remove();
+    component = null;
+  });
+
+  function initializeRebootPage(): Promise<void> {
+    assert(!component);
+    component = document.createElement(RebootPage.is);
+    assert(component);
+    document.body.appendChild(component);
+
+    return flushTasks();
+  }
+
+  // Verify the page initializes and renders.
+  test('ComponentRenders', async () => {
+    await initializeRebootPage();
+
+    assert(component);
+    const basePage =
+        strictQuery('base-page', component.shadowRoot, HTMLElement);
+    assert(basePage);
+  });
+
+  // Verify the text content updates based on the error code.
+  test('ErrorCodeUpdatesPage', async () => {
+    await initializeRebootPage();
+
+    assert(component);
+    component.errorCode = RmadErrorCode.kExpectReboot;
+
+    // The displayed value for how many seconds to wait before the reboot or
+    // shutdown.
+    const delayDuration = 3;
+    const title = strictQuery('#title', component.shadowRoot, HTMLElement);
+    const instructions =
+        strictQuery('#instructions', component.shadowRoot, HTMLElement);
+    assertEquals(
+        loadTimeData.getString('rebootPageTitle'), title.textContent!.trim());
+    assertEquals(
+        loadTimeData.getStringF('rebootPageMessage', delayDuration),
+        instructions.textContent!.trim());
+
+    component.errorCode = RmadErrorCode.kExpectShutdown;
+    assertEquals(
+        loadTimeData.getString('shutdownPageTitle'), title.textContent!.trim());
+    assertEquals(
+        loadTimeData.getStringF('shutdownPageMessage', delayDuration),
+        instructions.textContent!.trim());
+  });
+});
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.cc b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.cc
index d180493..553cc84 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.cc
+++ b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.cc
@@ -111,6 +111,10 @@
           "mocha.run()");
 }
 
+IN_PROC_BROWSER_TEST_F(ShimlessRmaBrowserTest, RebootPage) {
+  RunTest("chromeos/shimless_rma/reboot_page_test.js", "mocha.run()");
+}
+
 }  // namespace
 
 }  // namespace ash
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.js b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.js
index fae0909..fe336e2 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.js
@@ -33,7 +33,6 @@
 };
 
 const tests = [
-  ['RebootPageTest', 'reboot_page_test.js'],
   [
     'ReimagingCalibrationFailedPageTest',
     'reimaging_calibration_failed_page_test.js'
diff --git a/chrome/test/data/webui/cr_components/chromeos/network/apn_list_test.js b/chrome/test/data/webui/cr_components/chromeos/network/apn_list_test.js
index 2383870..c48d9344 100644
--- a/chrome/test/data/webui/cr_components/chromeos/network/apn_list_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/network/apn_list_test.js
@@ -118,8 +118,12 @@
     id: '10',
   };
 
-  function getZeroStateText() {
-    return apnList.shadowRoot.querySelector('#zeroStateText');
+  function getZeroStateContent() {
+    return apnList.shadowRoot.getElementById('zeroStateContent');
+  }
+
+  function getApnSettingsZeroStateDescriptionWithAddLink() {
+    return getZeroStateContent().querySelector('localized-link');
   }
 
   setup(async function() {
@@ -131,8 +135,12 @@
   test('Check if APN description exists', async function() {
     assertTrue(!!apnList);
     const getDescriptionWithLink = () =>
-        apnList.shadowRoot.querySelector('localized-link');
+        apnList.shadowRoot.querySelector('#descriptionWithLink');
     assertTrue(!!getDescriptionWithLink());
+    assertEquals(
+        getDescriptionWithLink().localizedString.toString(),
+        apnList.i18nAdvanced('apnSettingsDescriptionWithLink').toString());
+
     const getDescriptionWithoutLink = () =>
         apnList.shadowRoot.querySelector('#descriptionNoLink');
     assertFalse(!!getDescriptionWithoutLink());
@@ -142,6 +150,9 @@
     assertFalse(!!getDescriptionWithLink());
     assertTrue(!!getDescriptionWithoutLink());
     assertEquals(
+        getDescriptionWithoutLink().innerHTML.trim(),
+        apnList.i18n('apnSettingsDescriptionNoLink').toString());
+    assertEquals(
         'assertive',
         apnList.shadowRoot.querySelector('#apnDescription').ariaLive);
   });
@@ -153,16 +164,43 @@
     await flushTasks();
     assertEquals(
         apnList.shadowRoot.querySelectorAll('apn-list-item').length, 0);
-    assertTrue(!!getZeroStateText());
+    assertTrue(!!getZeroStateContent(), 'Expected zero state text to show');
+
+    const getApnDetailDialog = () =>
+        apnList.shadowRoot.querySelector('apn-detail-dialog');
+
+    apnList.shouldOmitLinks = false;
+    await flushTasks();
+
+    const localizedLink = getApnSettingsZeroStateDescriptionWithAddLink();
+    assertTrue(!!localizedLink, 'No link is present');
+    const testDetail = {event: {preventDefault: () => {}}};
+    assertFalse(
+        !!getApnDetailDialog(), 'Detail dialog shows when it should not');
+    localizedLink.dispatchEvent(
+        new CustomEvent('link-clicked', {bubbles: false, detail: testDetail}));
+    await flushTasks();
+
+    assertTrue(
+        !!getApnDetailDialog(), 'Detail dialog does not show when it should');
+    assertEquals(
+        ApnDetailDialogMode.CREATE, getApnDetailDialog().mode,
+        'Detail dialog is not in create mode');
   });
 
   test('Error states', async function() {
     apnList.managedCellularProperties = {};
     await flushTasks();
-    assertTrue(!!getZeroStateText());
+    assertTrue(!!getZeroStateContent(), 'No zero state content is present');
+    assertTrue(
+        !!getApnSettingsZeroStateDescriptionWithAddLink(),
+        'No link is present');
+
     assertEquals(
-        apnList.i18n('apnSettingsZeroStateDescription'),
-        getZeroStateText().querySelector('div').innerText);
+        getApnSettingsZeroStateDescriptionWithAddLink()
+            .localizedString.toString(),
+        apnList.i18nAdvanced('apnSettingsZeroStateDescriptionWithAddLink')
+            .toString());
     const getErrorMessage = () =>
         apnList.shadowRoot.querySelector('#errorMessageContainer');
     assertFalse(!!getErrorMessage());
@@ -170,13 +208,13 @@
     // Set as non-APN-related error.
     apnList.errorState = 'connect-failed';
     await flushTasks();
-    assertTrue(!!getZeroStateText());
+    assertTrue(!!getZeroStateContent());
     assertFalse(!!getErrorMessage());
 
     // Set as APN-related error.
     apnList.errorState = 'invalid-apn';
     await flushTasks();
-    assertFalse(!!getZeroStateText());
+    assertFalse(!!getZeroStateContent());
     assertTrue(!!getErrorMessage());
     const getErrorMessageText = () =>
         getErrorMessage().querySelector('#errorMessage').innerHTML.trim();
@@ -189,7 +227,7 @@
       customApnList: [customApnDefaultEnabled],
     };
     await flushTasks();
-    assertFalse(!!getZeroStateText());
+    assertFalse(!!getZeroStateContent());
     assertTrue(!!getErrorMessage());
     assertEquals(
         apnList.i18n('apnSettingsCustomApnsErrorMessage'),
@@ -200,7 +238,7 @@
       customApnList: [customApnDefaultDisabled],
     };
     await flushTasks();
-    assertFalse(!!getZeroStateText());
+    assertFalse(!!getZeroStateContent());
     assertTrue(!!getErrorMessage());
     assertEquals(
         apnList.i18n('apnSettingsDatabaseApnsErrorMessage'),
@@ -214,7 +252,7 @@
       },
     };
     await flushTasks();
-    assertFalse(!!getZeroStateText());
+    assertFalse(!!getZeroStateContent());
     assertFalse(!!getErrorMessage());
     const apns = apnList.shadowRoot.querySelectorAll('apn-list-item');
     assertEquals(apns.length, 1);
@@ -227,7 +265,7 @@
     await flushTasks();
     const apns = apnList.shadowRoot.querySelectorAll('apn-list-item');
     assertEquals(apns.length, 0);
-    assertTrue(!!getZeroStateText());
+    assertTrue(!!getZeroStateContent());
   });
 
   test(
@@ -241,7 +279,7 @@
         assertTrue(OncMojo.apnMatch(apns[0].apn, customApn1));
         assertTrue(OncMojo.apnMatch(apns[1].apn, customApn2));
         assertFalse(apns[0].isConnected);
-        assertFalse(!!getZeroStateText());
+        assertFalse(!!getZeroStateContent());
       });
 
   test(
@@ -258,7 +296,7 @@
         assertEquals(apns.length, 1);
         assertTrue(OncMojo.apnMatch(apns[0].apn, connectedApn));
         assertTrue(apns[0].isConnected);
-        assertFalse(!!getZeroStateText());
+        assertFalse(!!getZeroStateContent());
       });
 
   test(
@@ -278,7 +316,7 @@
         assertTrue(OncMojo.apnMatch(apns[1].apn, customApn1));
         assertTrue(OncMojo.apnMatch(apns[2].apn, customApn2));
         assertTrue(apns[0].isConnected);
-        assertFalse(!!getZeroStateText());
+        assertFalse(!!getZeroStateContent());
       });
 
   test('Connected APN is inside custom APN list.', async function() {
@@ -296,7 +334,7 @@
     assertTrue(OncMojo.apnMatch(apns[1].apn, customApn1));
     assertTrue(OncMojo.apnMatch(apns[2].apn, customApn2));
     assertTrue(apns[0].isConnected);
-    assertFalse(!!getZeroStateText());
+    assertFalse(!!getZeroStateContent());
   });
 
   test('Connected APN is the only apn in custom APN list.', async function() {
@@ -309,7 +347,7 @@
     assertEquals(apns.length, 1);
     assertTrue(OncMojo.apnMatch(apns[0].apn, connectedApn));
     assertTrue(apns[0].isConnected);
-    assertFalse(!!getZeroStateText());
+    assertFalse(!!getZeroStateContent());
 
     // Simulate the APN no longer being connected.
     apnList.managedCellularProperties = {
@@ -320,7 +358,7 @@
     assertEquals(apns.length, 1);
     assertTrue(OncMojo.apnMatch(apns[0].apn, connectedApn));
     assertFalse(apns[0].isConnected);
-    assertFalse(!!getZeroStateText());
+    assertFalse(!!getZeroStateContent());
   });
 
   test(
diff --git a/chrome/test/data/webui/cr_components/chromeos/network/apn_selection_dialog_test.js b/chrome/test/data/webui/cr_components/chromeos/network/apn_selection_dialog_test.js
index 48631c1..257dbb3 100644
--- a/chrome/test/data/webui/cr_components/chromeos/network/apn_selection_dialog_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/network/apn_selection_dialog_test.js
@@ -38,6 +38,7 @@
         mojoApi);
     apnSelectionDialog = document.createElement('apn-selection-dialog');
     apnSelectionDialog.guid = 'fake-guid';
+    apnSelectionDialog.shouldOmitLinks = false;
     document.body.appendChild(apnSelectionDialog);
     return waitAfterNextRender(apnSelectionDialog);
   });
@@ -47,25 +48,22 @@
     mojoApi.resetForTest();
   });
 
-  test('Element contains dialog', () => {
+  test('Element contains dialog', async () => {
     const dialog = apnSelectionDialog.shadowRoot.querySelector('cr-dialog');
     assertTrue(!!dialog);
     assertTrue(dialog.open);
     const apnSelectionDialogTitle =
         apnSelectionDialog.shadowRoot.querySelector('#apnSelectionDialogTitle');
-    assertTrue(!!apnSelectionDialogTitle);
+    assertTrue(!!apnSelectionDialogTitle, 'Title does not exist');
     assertEquals(
         apnSelectionDialog.i18n('apnSelectionDialogTitle'),
-        apnSelectionDialogTitle.innerText);
+        apnSelectionDialogTitle.innerText, 'Inner text does not match');
 
-    const apnSelectionDialogDescription =
-        apnSelectionDialog.shadowRoot.querySelector(
-            '#apnSelectionDialogDescription');
-    assertTrue(!!apnSelectionDialogDescription);
-    assertEquals(
-        apnSelectionDialog.i18n('apnSelectionDialogDescription'),
-        apnSelectionDialogDescription.innerText);
-    assertEquals('polite', apnSelectionDialogDescription.ariaLive);
+    const getDescriptionWithLink = () =>
+        apnSelectionDialog.shadowRoot.querySelector('localized-link');
+    assertTrue(
+        !!getDescriptionWithLink(),
+        'Description does not contain link when it should');
 
     const apnSelectionActionBtn =
         apnSelectionDialog.shadowRoot.querySelector('#apnSelectionActionBtn');
@@ -79,9 +77,25 @@
     assertTrue(!!apnSelectionCancelBtn);
     assertEquals(
         apnSelectionDialog.i18n('apnDetailDialogCancel'),
-        apnSelectionCancelBtn.innerText);
+        apnSelectionCancelBtn.innerText,
+    );
     assertEquals(
         apnSelectionCancelBtn, apnSelectionDialog.shadowRoot.activeElement);
+
+    apnSelectionDialog.shouldOmitLinks = true;
+    await flushTasks();
+    assertFalse(
+        !!getDescriptionWithLink(),
+        'Description contains link when it should not');
+    const apnSelectionDialogDescription =
+        apnSelectionDialog.shadowRoot.querySelector(
+            '#apnSelectionDialogDescription');
+    assertTrue(!!apnSelectionDialogDescription, 'Description does not show');
+    assertEquals(
+        apnSelectionDialog.i18n('apnSelectionDialogDescription'),
+        apnSelectionDialogDescription.innerText,
+        'Description does not contain expected text');
+    assertEquals('polite', apnSelectionDialogDescription.ariaLive);
   });
 
   test('No apnList', () => {
diff --git a/chrome/test/data/webui/cr_components/history_embeddings/filter_chips_test.ts b/chrome/test/data/webui/cr_components/history_embeddings/filter_chips_test.ts
index 5751b86..f61b05b 100644
--- a/chrome/test/data/webui/cr_components/history_embeddings/filter_chips_test.ts
+++ b/chrome/test/data/webui/cr_components/history_embeddings/filter_chips_test.ts
@@ -5,7 +5,7 @@
 import 'chrome://history/strings.m.js';
 import 'chrome://resources/cr_components/history_embeddings/filter_chips.js';
 
-import type {HistoryEmbeddingsFilterChips} from 'chrome://resources/cr_components/history_embeddings/filter_chips.js';
+import type {HistoryEmbeddingsFilterChips, Suggestion} from 'chrome://resources/cr_components/history_embeddings/filter_chips.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
@@ -16,9 +16,9 @@
 
   setup(() => {
     loadTimeData.overrideValues({
-      historyEmbeddingsSuggestion1: 'suggestion 1',
-      historyEmbeddingsSuggestion2: 'suggestion 2',
-      historyEmbeddingsSuggestion3: 'suggestion 3',
+      historyEmbeddingsSuggestion1: 'yesterday',
+      historyEmbeddingsSuggestion2: 'last 7 days',
+      historyEmbeddingsSuggestion3: 'last 30 days',
     });
 
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
@@ -54,29 +54,9 @@
     assertFalse(notifyEvent.detail.value);
   });
 
-  test('UpdatesSuggestionsOnBinding', () => {
-    function getSelectedChips() {
-      return element.shadowRoot!.querySelectorAll(
-          '#suggestions cr-chip[selected]');
-    }
-    assertEquals(0, getSelectedChips().length);
-
-    ['suggestion 1', 'suggestion 2', 'suggestion 3'].forEach(suggestion => {
-      // Update the search query binding with each suggestion's text should
-      // mark that suggestion as selected.
-      element.selectedSuggestion = suggestion;
-      const selectedChips = getSelectedChips();
-      assertEquals(1, selectedChips.length);
-      assertEquals(suggestion, selectedChips[0]!.textContent!.trim());
-    });
-
-    element.selectedSuggestion = undefined;
-    assertEquals(0, getSelectedChips().length);
-  });
-
   test('SelectingSuggestionsDispatchesEvents', async () => {
     async function clickChipAndGetSelectedSuggestion(chip: HTMLElement):
-        Promise<string> {
+        Promise<Suggestion> {
       const eventPromise =
           eventToPromise('selected-suggestion-changed', element);
       chip.click();
@@ -84,29 +64,39 @@
       return event.detail.value;
     }
 
+    function assertDaysFromToday(days: number, date: Date) {
+      const today = new Date();
+      today.setHours(0, 0, 0, 0);
+      const dateDiff = today.getTime() - date.getTime();
+      assertEquals(days, Math.round(dateDiff / 1000 / 60 / 60 / 24));
+    }
+
     const suggestions = element.shadowRoot!.querySelectorAll<HTMLElement>(
         '#suggestions cr-chip');
-    assertEquals(
-        'suggestion 1',
-        await clickChipAndGetSelectedSuggestion(suggestions[0]!));
-    assertEquals(
-        'suggestion 2',
-        await clickChipAndGetSelectedSuggestion(suggestions[1]!));
-    assertEquals(
-        'suggestion 3',
-        await clickChipAndGetSelectedSuggestion(suggestions[2]!));
+    const yesterday = await clickChipAndGetSelectedSuggestion(suggestions[0]!);
+    assertEquals('yesterday', yesterday.label);
+    assertDaysFromToday(1, yesterday.timeRangeStart);
+    const last7Days = await clickChipAndGetSelectedSuggestion(suggestions[1]!);
+    assertEquals('last 7 days', last7Days.label);
+    assertDaysFromToday(7, last7Days.timeRangeStart);
+    const last30Days = await clickChipAndGetSelectedSuggestion(suggestions[2]!);
+    assertEquals('last 30 days', last30Days.label);
+    assertDaysFromToday(30, last30Days.timeRangeStart);
   });
 
   test('UnselectingSuggestionsDispatchesEvent', async () => {
-    element.selectedSuggestion = 'suggestion 1';
-    const selectedChip = element.shadowRoot!.querySelector<HTMLElement>(
-        '#suggestions cr-chip[selected]');
-    assertTrue(!!selectedChip);
-    assertEquals('suggestion 1', selectedChip.textContent!.trim());
+    const firstChip =
+        element.shadowRoot!.querySelector<HTMLElement>('#suggestions cr-chip')!;
+    const selectPromise =
+        eventToPromise('selected-suggestion-changed', element);
+    firstChip.click();
+    const selectEvent = await selectPromise;
+    assertEquals('yesterday', selectEvent.detail.value.label);
 
-    const eventPromise = eventToPromise('selected-suggestion-changed', element);
-    selectedChip.click();
-    const event = await eventPromise;
-    assertEquals(undefined, event.detail.value);
+    const unselectPromise =
+        eventToPromise('selected-suggestion-changed', element);
+    firstChip.click();
+    const unselectEvent = await unselectPromise;
+    assertEquals(undefined, unselectEvent.detail.value);
   });
 });
diff --git a/chrome/test/data/webui/cr_elements/cr_button_test.ts b/chrome/test/data/webui/cr_elements/cr_button_test.ts
index 6e50a3ad..f5c1913f 100644
--- a/chrome/test/data/webui/cr_elements/cr_button_test.ts
+++ b/chrome/test/data/webui/cr_elements/cr_button_test.ts
@@ -147,11 +147,7 @@
     await whenPrefixSlotchange;
 
     assertEquals('8px', buttonStyle.gap);
-    assertEquals('8px', buttonStyle.padding);
-
-    document.documentElement.toggleAttribute('chrome-refresh-2023', true);
     assertEquals('8px 16px 8px 12px', buttonStyle.padding);
-    document.documentElement.removeAttribute('chrome-refresh-2023');
 
     const whenPrefixSlotRemoved =
         eventToPromise('slotchange', button.$.prefixIcon);
@@ -168,10 +164,6 @@
     await whenSuffixSlotchange;
 
     assertEquals('8px', buttonStyle.gap);
-    assertEquals('8px', buttonStyle.padding);
-
-    document.documentElement.toggleAttribute('chrome-refresh-2023', true);
     assertEquals('8px 12px 8px 16px', buttonStyle.padding);
-    document.documentElement.removeAttribute('chrome-refresh-2023');
   });
 });
diff --git a/chrome/test/data/webui/lens/BUILD.gn b/chrome/test/data/webui/lens/BUILD.gn
index d258ba2..501a107 100644
--- a/chrome/test/data/webui/lens/BUILD.gn
+++ b/chrome/test/data/webui/lens/BUILD.gn
@@ -11,9 +11,12 @@
     "overlay/test_overlay_browser_proxy.ts",
     "overlay/selection_overlay_test.ts",
     "overlay/text_selection_test.ts",
+    "overlay/object_selection_test.ts",
     "overlay/region_selection_test.ts",
     "side_panel/results_frame_test.ts",
     "side_panel/test_side_panel_browser_proxy.ts",
+    "utils/object_utils.ts",
+    "utils/selection_utils.ts",
     "utils/text_utils.ts",
   ]
 
diff --git a/chrome/test/data/webui/lens/lens_webui_browsertest.cc b/chrome/test/data/webui/lens/lens_webui_browsertest.cc
index 31e9666..487e6b81 100644
--- a/chrome/test/data/webui/lens/lens_webui_browsertest.cc
+++ b/chrome/test/data/webui/lens/lens_webui_browsertest.cc
@@ -105,6 +105,10 @@
   RunOverlayTest("lens/overlay/text_selection_test.js", "mocha.run()");
 }
 
+IN_PROC_BROWSER_TEST_F(LensOverlayTest, ObjectSelection) {
+  RunOverlayTest("lens/overlay/object_selection_test.js", "mocha.run()");
+}
+
 IN_PROC_BROWSER_TEST_F(LensOverlayTest, SelectionOverlay) {
   RunOverlayTest("lens/overlay/selection_overlay_test.js", "mocha.run()");
 }
diff --git a/chrome/test/data/webui/lens/overlay/object_selection_test.ts b/chrome/test/data/webui/lens/overlay/object_selection_test.ts
new file mode 100644
index 0000000..0db0747
--- /dev/null
+++ b/chrome/test/data/webui/lens/overlay/object_selection_test.ts
@@ -0,0 +1,109 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome-untrusted://lens/selection_overlay.js';
+
+import type {RectF} from '//resources/mojo/ui/gfx/geometry/mojom/geometry.mojom-webui.js';
+import {BrowserProxyImpl} from 'chrome-untrusted://lens/browser_proxy.js';
+import type {LensPageRemote} from 'chrome-untrusted://lens/lens.mojom-webui.js';
+import type {OverlayObject} from 'chrome-untrusted://lens/overlay_object.mojom-webui.js';
+import type {SelectionOverlayElement} from 'chrome-untrusted://lens/selection_overlay.js';
+import {assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome-untrusted://webui-test/polymer_test_util.js';
+
+import {assertBoxesWithinThreshold, createObject} from '../utils/object_utils.js';
+import {simulateClick} from '../utils/selection_utils.js';
+
+import {TestLensOverlayBrowserProxy} from './test_overlay_browser_proxy.js';
+
+
+suite('ObjectSelection', function() {
+  let testBrowserProxy: TestLensOverlayBrowserProxy;
+  let selectionOverlayElement: SelectionOverlayElement;
+  let callbackRouterRemote: LensPageRemote;
+  let objects: OverlayObject[];
+
+  setup(async () => {
+    // Resetting the HTML needs to be the first thing we do in setup to
+    // guarantee that any singleton instances don't change while any UI is still
+    // attached to the DOM.
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+
+    testBrowserProxy = new TestLensOverlayBrowserProxy();
+    callbackRouterRemote =
+        testBrowserProxy.callbackRouter.$.bindNewPipeAndPassRemote();
+    BrowserProxyImpl.setInstance(testBrowserProxy);
+
+    selectionOverlayElement = document.createElement('lens-selection-overlay');
+    document.body.appendChild(selectionOverlayElement);
+    // Since the size of the Selection Overlay is based on the screenshot which
+    // is not loaded in the test, we need to force the overlay to take up the
+    // viewport.
+    selectionOverlayElement.$.selectionOverlay.style.width = '100%';
+    selectionOverlayElement.$.selectionOverlay.style.height = '100%';
+    await flushTasks();
+    await addObjects();
+  });
+
+  // Normalizes the given values to the size of selection overlay.
+  function normalizedBox(box: RectF): RectF {
+    const boundingRect = selectionOverlayElement.getBoundingClientRect();
+    return {
+      x: box.x / boundingRect.width,
+      y: box.y / boundingRect.height,
+      width: box.width / boundingRect.width,
+      height: box.height / boundingRect.height,
+    };
+  }
+
+  function addObjects() {
+    objects = [
+      {x: 20, y: 15, width: 20, height: 10},
+      {x: 120, y: 15, width: 30, height: 10},
+      {x: 70, y: 35, width: 50, height: 20},
+      {x: 320, y: 50, width: 80, height: 30},
+      {x: 320, y: 50, width: 40, height: 20},
+      {x: 320, y: 50, width: 60, height: 25},
+    ].map((rect, i) => createObject(i.toString(), normalizedBox(rect)));
+    callbackRouterRemote.objectsReceived(objects);
+    return flushTasks();
+  }
+
+  function getRenderedObjects() {
+    return selectionOverlayElement.$.objectSelectionLayer
+        .getObjectNodesForTesting();
+  }
+
+  test('verify that objects render on the page', () => {
+    const wordsOnPage = getRenderedObjects();
+
+    assertEquals(6, wordsOnPage.length);
+  });
+
+  test(
+      `verify that tapping an object issues lens request via mojo`,
+      async () => {
+        await simulateClick(selectionOverlayElement, {x: 120, y: 15});
+
+        const rect =
+            await testBrowserProxy.handler.whenCalled('issueLensRequest');
+        assertBoxesWithinThreshold(objects[1]!.geometry.boundingBox, rect);
+      });
+
+  test(`verify that tapping off an object does nothing`, async () => {
+    await simulateClick(selectionOverlayElement, {x: 120, y: 35});
+
+    assertEquals(0, testBrowserProxy.handler.getCallCount('issueLensRequest'));
+  });
+
+  test(
+      `verify that smaller objects have priority over larger objects`,
+      async () => {
+        await simulateClick(selectionOverlayElement, {x: 320, y: 50});
+
+        const rect =
+            await testBrowserProxy.handler.whenCalled('issueLensRequest');
+        assertBoxesWithinThreshold(objects[4]!.geometry.boundingBox, rect);
+      });
+});
diff --git a/chrome/test/data/webui/lens/overlay/region_selection_test.ts b/chrome/test/data/webui/lens/overlay/region_selection_test.ts
index 8da9c9e..c9c89fb3 100644
--- a/chrome/test/data/webui/lens/overlay/region_selection_test.ts
+++ b/chrome/test/data/webui/lens/overlay/region_selection_test.ts
@@ -10,7 +10,9 @@
 import type {CenterRotatedBox} from 'chrome-untrusted://lens/geometry.mojom-webui.js';
 import type {SelectionOverlayElement} from 'chrome-untrusted://lens/selection_overlay.js';
 import {assertDeepEquals, assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js';
-import {flushTasks} from 'chrome-untrusted://webui-test/polymer_test_util.js';
+import {waitAfterNextRender} from 'chrome-untrusted://webui-test/polymer_test_util.js';
+
+import {simulateDrag} from '../utils/selection_utils.js';
 
 import {TestLensOverlayBrowserProxy} from './test_overlay_browser_proxy.js';
 
@@ -19,10 +21,14 @@
   let selectionOverlayElement: SelectionOverlayElement;
 
   setup(() => {
+    // Resetting the HTML needs to be the first thing we do in setup to
+    // guarantee that any singleton instances don't change while any UI is still
+    // attached to the DOM.
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+
     testBrowserProxy = new TestLensOverlayBrowserProxy();
     BrowserProxyImpl.setInstance(testBrowserProxy);
 
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
     selectionOverlayElement = document.createElement('lens-selection-overlay');
     // Position absolutely so we can handle logic of drag ending off this
     // element.
@@ -32,34 +38,9 @@
     selectionOverlayElement.style.top = '50px';
     selectionOverlayElement.style.left = '50px';
     document.body.appendChild(selectionOverlayElement);
-    return flushTasks();
+    return waitAfterNextRender(selectionOverlayElement);
   });
 
-  function createPrimaryClickPointerEvent(
-      eventType: string, point: Point): PointerEvent {
-    return new PointerEvent(eventType, {
-      pointerId: 1,
-      bubbles: true,
-      button: 0,
-      clientX: point.x,
-      clientY: point.y,
-      isPrimary: true,
-    });
-  }
-
-  function doDrag(fromPoint: Point, toPoint: Point): Promise<void> {
-    const pointerDownEvent =
-        createPrimaryClickPointerEvent('pointerdown', fromPoint);
-    const pointerMoveEvent =
-        createPrimaryClickPointerEvent('pointermove', toPoint);
-    const pointerUpEvent = createPrimaryClickPointerEvent('pointerup', toPoint);
-
-    selectionOverlayElement.dispatchEvent(pointerDownEvent);
-    selectionOverlayElement.dispatchEvent(pointerMoveEvent);
-    selectionOverlayElement.dispatchEvent(pointerUpEvent);
-    return flushTasks();
-  }
-
   // Normalizes the given values to the size of selection overlay.
   function normalizedBox(box: RectF): RectF {
     const boundingRect = selectionOverlayElement.getBoundingClientRect();
@@ -78,7 +59,7 @@
     // call that already happened.
     testBrowserProxy.handler.resetResolver('issueLensRequest');
 
-    await doDrag(fromPoint, toPoint);
+    await simulateDrag(selectionOverlayElement, fromPoint, toPoint);
     const rect = await testBrowserProxy.handler.whenCalled('issueLensRequest');
     assertDeepEquals(expectedRect, rect);
   }
@@ -201,7 +182,7 @@
 
   test('verify canvas resizes', async () => {
     selectionOverlayElement.$.regionSelectionLayer.setCanvasSizeTo(50, 50);
-    await flushTasks();
+    await waitAfterNextRender(selectionOverlayElement.$.regionSelectionLayer);
     assertEquals(
         50,
         selectionOverlayElement.$.regionSelectionLayer.$.regionSelectionCanvas
@@ -212,7 +193,7 @@
             .height);
 
     selectionOverlayElement.$.regionSelectionLayer.setCanvasSizeTo(100, 100);
-    await flushTasks();
+    await waitAfterNextRender(selectionOverlayElement.$.regionSelectionLayer);
     assertEquals(
         100,
         selectionOverlayElement.$.regionSelectionLayer.$.regionSelectionCanvas
diff --git a/chrome/test/data/webui/lens/overlay/selection_overlay_test.ts b/chrome/test/data/webui/lens/overlay/selection_overlay_test.ts
index db1429fa..ded8894a 100644
--- a/chrome/test/data/webui/lens/overlay/selection_overlay_test.ts
+++ b/chrome/test/data/webui/lens/overlay/selection_overlay_test.ts
@@ -9,31 +9,34 @@
 import {CenterRotatedBox_CoordinateType} from 'chrome-untrusted://lens/geometry.mojom-webui.js';
 import type {CenterRotatedBox} from 'chrome-untrusted://lens/geometry.mojom-webui.js';
 import type {LensPageRemote} from 'chrome-untrusted://lens/lens.mojom-webui.js';
+import type {OverlayObject} from 'chrome-untrusted://lens/overlay_object.mojom-webui.js';
 import type {SelectionOverlayElement} from 'chrome-untrusted://lens/selection_overlay.js';
 import {assertDeepEquals, assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js';
 import {flushTasks, waitAfterNextRender} from 'chrome-untrusted://webui-test/polymer_test_util.js';
 
+import {assertBoxesWithinThreshold, createObject} from '../utils/object_utils.js';
+import {simulateClick, simulateDrag} from '../utils/selection_utils.js';
 import {createLine, createParagraph, createText, createWord} from '../utils/text_utils.js';
 
 import {TestLensOverlayBrowserProxy} from './test_overlay_browser_proxy.js';
 
-interface Point {
-  x: number;
-  y: number;
-}
-
 suite('SelectionOverlay', function() {
   let testBrowserProxy: TestLensOverlayBrowserProxy;
   let selectionOverlayElement: SelectionOverlayElement;
   let callbackRouterRemote: LensPageRemote;
+  let objects: OverlayObject[];
 
   setup(() => {
+    // Resetting the HTML needs to be the first thing we do in setup to
+    // guarantee that any singleton instances don't change while any UI is still
+    // attached to the DOM.
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+
     testBrowserProxy = new TestLensOverlayBrowserProxy();
     callbackRouterRemote =
         testBrowserProxy.callbackRouter.$.bindNewPipeAndPassRemote();
     BrowserProxyImpl.setInstance(testBrowserProxy);
 
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
     selectionOverlayElement = document.createElement('lens-selection-overlay');
     document.body.appendChild(selectionOverlayElement);
     // Since the size of the Selection Overlay is based on the screenshot which
@@ -55,7 +58,7 @@
     };
   }
 
-  async function addWords() {
+  function addWords() {
     const text = createText([
       createParagraph([
         createLine([
@@ -72,25 +75,12 @@
     return flushTasks();
   }
 
-  function createPointerEvent(eventType: string, point: Point): PointerEvent {
-    return new PointerEvent(eventType, {
-      pointerId: 1,
-      bubbles: true,
-      button: 0,
-      clientX: point.x,
-      clientY: point.y,
-      isPrimary: true,
-    });
-  }
-
-  function simulateDrag(fromPoint: Point, toPoint: Point): Promise<void> {
-    const pointerDownEvent = createPointerEvent('pointerdown', fromPoint);
-    const pointerMoveEvent = createPointerEvent('pointermove', toPoint);
-    const pointerUpEvent = createPointerEvent('pointerup', toPoint);
-
-    selectionOverlayElement.dispatchEvent(pointerDownEvent);
-    selectionOverlayElement.dispatchEvent(pointerMoveEvent);
-    selectionOverlayElement.dispatchEvent(pointerUpEvent);
+  function addObjects() {
+    objects = [
+      {x: 80, y: 20, width: 25, height: 10},
+      {x: 70, y: 35, width: 20, height: 10},
+    ].map((rect, i) => createObject(i.toString(), normalizedBox(rect)));
+    callbackRouterRemote.objectsReceived(objects);
     return flushTasks();
   }
 
@@ -103,7 +93,7 @@
         const wordEl = selectionOverlayElement.$.textSelectionLayer
                            .getWordNodesForTesting()[0]!;
         await simulateDrag(
-            {
+            selectionOverlayElement, {
               x: wordEl.getBoundingClientRect().left + 5,
               y: wordEl.getBoundingClientRect().top + 5,
             },
@@ -126,7 +116,7 @@
           x: wordEl.getBoundingClientRect().left + 5,
           y: wordEl.getBoundingClientRect().top + 5,
         };
-        await simulateDrag({x: 0, y: 0}, dragEnd);
+        await simulateDrag(selectionOverlayElement, {x: 0, y: 0}, dragEnd);
 
         const expectedRect: CenterRotatedBox = {
           box: normalizedBox({
@@ -173,4 +163,50 @@
             selectionOverlayElement.$.regionSelectionLayer.$
                 .regionSelectionCanvas.height);
       });
+
+  test(
+      `verify that only objects respond to taps, even when text overlaps`,
+      async () => {
+        await Promise.all([addWords(), addObjects()]);
+
+        await simulateClick(selectionOverlayElement, {x: 80, y: 20});
+
+        const rect =
+            await testBrowserProxy.handler.whenCalled('issueLensRequest');
+        assertBoxesWithinThreshold(objects[0]!.geometry.boundingBox, rect);
+      });
+
+  test(
+      `verify that dragging performs region search, even when an object
+      overlaps`,
+      async () => {
+        await addObjects();
+
+        // Drag that starts and ends inside the bounding box of an object.
+        const objectEl = selectionOverlayElement.$.objectSelectionLayer
+                             .getObjectNodesForTesting()[1]!;
+        const dragStart = {
+          x: objectEl.getBoundingClientRect().left + 1,
+          y: objectEl.getBoundingClientRect().top + 1,
+        };
+        const dragEnd = {
+          x: objectEl.getBoundingClientRect().right - 1,
+          y: objectEl.getBoundingClientRect().bottom - 1,
+        };
+        await simulateDrag(selectionOverlayElement, dragStart, dragEnd);
+
+        const expectedRect: CenterRotatedBox = {
+          box: normalizedBox({
+            x: (dragStart.x + dragEnd.x) / 2,
+            y: (dragStart.y + dragEnd.y) / 2,
+            width: dragEnd.x - dragStart.x,
+            height: dragEnd.y - dragStart.y,
+          }),
+          rotation: 0,
+          coordinateType: CenterRotatedBox_CoordinateType.kNormalized,
+        };
+        const rect =
+            await testBrowserProxy.handler.whenCalled('issueLensRequest');
+        assertDeepEquals(expectedRect, rect);
+      });
 });
diff --git a/chrome/test/data/webui/lens/overlay/text_selection_test.ts b/chrome/test/data/webui/lens/overlay/text_selection_test.ts
index 6a0feff..506e645 100644
--- a/chrome/test/data/webui/lens/overlay/text_selection_test.ts
+++ b/chrome/test/data/webui/lens/overlay/text_selection_test.ts
@@ -4,13 +4,14 @@
 
 import 'chrome-untrusted://lens/selection_overlay.js';
 
-import type {Point, RectF} from '//resources/mojo/ui/gfx/geometry/mojom/geometry.mojom-webui.js';
+import type {RectF} from '//resources/mojo/ui/gfx/geometry/mojom/geometry.mojom-webui.js';
 import {BrowserProxyImpl} from 'chrome-untrusted://lens/browser_proxy.js';
 import type {LensPageRemote} from 'chrome-untrusted://lens/lens.mojom-webui.js';
 import type {SelectionOverlayElement} from 'chrome-untrusted://lens/selection_overlay.js';
 import {assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome-untrusted://webui-test/polymer_test_util.js';
 
+import {simulateClick, simulateDrag} from '../utils/selection_utils.js';
 import {createLine, createParagraph, createText, createWord} from '../utils/text_utils.js';
 
 import {TestLensOverlayBrowserProxy} from './test_overlay_browser_proxy.js';
@@ -29,12 +30,16 @@
   let callbackRouterRemote: LensPageRemote;
 
   setup(async () => {
+    // Resetting the HTML needs to be the first thing we do in setup to
+    // guarantee that any singleton instances don't change while any UI is still
+    // attached to the DOM.
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+
     testBrowserProxy = new TestLensOverlayBrowserProxy();
     callbackRouterRemote =
         testBrowserProxy.callbackRouter.$.bindNewPipeAndPassRemote();
     BrowserProxyImpl.setInstance(testBrowserProxy);
 
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
     selectionOverlayElement = document.createElement('lens-selection-overlay');
     document.body.appendChild(selectionOverlayElement);
     // Since the size of the Selection Overlay is based on the screenshot which
@@ -90,37 +95,6 @@
     return flushTasks();
   }
 
-  function createPointerEvent(eventType: string, point: Point): PointerEvent {
-    return new PointerEvent(eventType, {
-      pointerId: 1,
-      bubbles: true,
-      button: 0,
-      clientX: point.x,
-      clientY: point.y,
-      isPrimary: true,
-    });
-  }
-
-  async function simulateClick(point: Point): Promise<void> {
-    const pointerDownEvent = createPointerEvent('pointerdown', point);
-    const pointerUpEvent = createPointerEvent('pointerup', point);
-
-    selectionOverlayElement.dispatchEvent(pointerDownEvent);
-    selectionOverlayElement.dispatchEvent(pointerUpEvent);
-    return flushTasks();
-  }
-
-  async function simulateDrag(fromPoint: Point, toPoint: Point): Promise<void> {
-    const pointerDownEvent = createPointerEvent('pointerdown', fromPoint);
-    const pointerMoveEvent = createPointerEvent('pointermove', toPoint);
-    const pointerUpEvent = createPointerEvent('pointerup', toPoint);
-
-    selectionOverlayElement.dispatchEvent(pointerDownEvent);
-    selectionOverlayElement.dispatchEvent(pointerMoveEvent);
-    selectionOverlayElement.dispatchEvent(pointerUpEvent);
-    return flushTasks();
-  }
-
   function getRenderedWords(): NodeListOf<Element> {
     return selectionOverlayElement.$.textSelectionLayer
         .getWordNodesForTesting();
@@ -143,6 +117,7 @@
 
     // Drag from beginning of first word to end of first word.
     await simulateDrag(
+        selectionOverlayElement,
         {x: firstWordBoundingBox.left + 2, y: firstWordBoundingBox.top + 2},
         {x: firstWordBoundingBox.right - 2, y: firstWordBoundingBox.top + 2});
 
@@ -163,7 +138,7 @@
 
         // Drag from first word onto second word.
         await simulateDrag(
-            {
+            selectionOverlayElement, {
               x: getCenterX(firstWordBoundingBox),
               y: getCenterY(firstWordBoundingBox),
             },
@@ -198,7 +173,7 @@
 
         // Drag from second word to off line and above third word.
         await simulateDrag(
-            {
+            selectionOverlayElement, {
               x: getCenterX(secondWordBoundingBox),
               y: getCenterY(secondWordBoundingBox),
             },
@@ -237,7 +212,7 @@
 
         // Drag from first word to the right of last word of paragraph.
         await simulateDrag(
-            {
+            selectionOverlayElement, {
               x: getCenterX(firstParagraphFirstWordBox),
               y: getCenterY(firstParagraphFirstWordBox),
             },
@@ -260,7 +235,7 @@
     // Drag from last word of first paragraph to second word of second
     // paragraph.
     await simulateDrag(
-        {
+        selectionOverlayElement, {
           x: getCenterX(firstParagraphLastWordBox),
           y: getCenterY(firstParagraphLastWordBox),
         },
@@ -288,7 +263,7 @@
   });
 
   test('verify that starting a drag off a word does nothing', async () => {
-    await simulateDrag({x: 0, y: 0}, {x: 70, y: 35});
+    await simulateDrag(selectionOverlayElement, {x: 0, y: 0}, {x: 70, y: 35});
 
     const highlightedWords = getHighlightedWords();
 
@@ -302,13 +277,16 @@
 
     // Highlight some words.
     await simulateDrag(
+        selectionOverlayElement,
         {x: getCenterX(firstWord), y: getCenterY(firstWord)},
         {x: getCenterX(secondWord), y: getCenterY(secondWord)});
     let highlightedWords = getHighlightedWords();
     assertEquals(2, highlightedWords.length);
 
     // Click on a word.
-    await simulateClick({x: getCenterX(firstWord), y: getCenterY(firstWord)});
+    await simulateClick(
+        selectionOverlayElement,
+        {x: getCenterX(firstWord), y: getCenterY(firstWord)});
 
     // Verify words unhighlight.
     highlightedWords = getHighlightedWords();
@@ -322,13 +300,14 @@
 
     // Highlight some words.
     await simulateDrag(
+        selectionOverlayElement,
         {x: getCenterX(firstWord), y: getCenterY(firstWord)},
         {x: getCenterX(secondWord), y: getCenterY(secondWord)});
     let highlightedWords = getHighlightedWords();
     assertEquals(2, highlightedWords.length);
 
     // Click off a word.
-    await simulateClick({x: 0, y: 0});
+    await simulateClick(selectionOverlayElement, {x: 0, y: 0});
 
     // Verify words unhighlight.
     highlightedWords = getHighlightedWords();
diff --git a/chrome/test/data/webui/lens/utils/object_utils.ts b/chrome/test/data/webui/lens/utils/object_utils.ts
new file mode 100644
index 0000000..053ab02
--- /dev/null
+++ b/chrome/test/data/webui/lens/utils/object_utils.ts
@@ -0,0 +1,34 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import type {RectF} from '//resources/mojo/ui/gfx/geometry/mojom/geometry.mojom-webui.js';
+import type {CenterRotatedBox} from 'chrome-untrusted://lens/geometry.mojom-webui.js';
+import {CenterRotatedBox_CoordinateType} from 'chrome-untrusted://lens/geometry.mojom-webui.js';
+import type {OverlayObject} from 'chrome-untrusted://lens/overlay_object.mojom-webui.js';
+import {assertEquals, assertLT} from 'chrome-untrusted://webui-test/chai_assert.js';
+
+export function assertBoxesWithinThreshold(
+    box1: CenterRotatedBox, box2: CenterRotatedBox) {
+  const threshold: number = 1e-6;
+
+  assertLT(Math.abs(box1.box.x - box2.box.x), threshold);
+  assertLT(Math.abs(box1.box.y - box2.box.y), threshold);
+  assertLT(Math.abs(box1.box.height - box2.box.height), threshold);
+  assertLT(Math.abs(box1.box.width - box2.box.width), threshold);
+  assertEquals(box1.rotation, box2.rotation);
+  assertEquals(box1.coordinateType, box2.coordinateType);
+}
+
+export function createObject(id: string, boundingBox: RectF): OverlayObject {
+  return {
+    id,
+    geometry: {
+      boundingBox: {
+        box: boundingBox,
+        rotation: 0,
+        coordinateType: CenterRotatedBox_CoordinateType.kNormalized,
+      },
+    },
+  };
+}
diff --git a/chrome/test/data/webui/lens/utils/selection_utils.ts b/chrome/test/data/webui/lens/utils/selection_utils.ts
new file mode 100644
index 0000000..f9fff1e
--- /dev/null
+++ b/chrome/test/data/webui/lens/utils/selection_utils.ts
@@ -0,0 +1,41 @@
+// 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 type {Point} from '//resources/mojo/ui/gfx/geometry/mojom/geometry.mojom-webui.js';
+import type {SelectionOverlayElement} from 'chrome-untrusted://lens/selection_overlay.js';
+import {flushTasks} from 'chrome-untrusted://webui-test/polymer_test_util.js';
+
+function createPointerEvent(eventType: string, point: Point): PointerEvent {
+  return new PointerEvent(eventType, {
+    pointerId: 1,
+    bubbles: true,
+    button: 0,
+    clientX: point.x,
+    clientY: point.y,
+    isPrimary: true,
+  });
+}
+
+export function simulateClick(
+    selectionOverlayElement: SelectionOverlayElement, point: Point) {
+  const pointerDownEvent = createPointerEvent('pointerdown', point);
+  const pointerUpEvent = createPointerEvent('pointerup', point);
+
+  selectionOverlayElement.dispatchEvent(pointerDownEvent);
+  selectionOverlayElement.dispatchEvent(pointerUpEvent);
+  return flushTasks();
+}
+
+export function simulateDrag(
+    selectionOverlayElement: SelectionOverlayElement, fromPoint: Point,
+    toPoint: Point) {
+  const pointerDownEvent = createPointerEvent('pointerdown', fromPoint);
+  const pointerMoveEvent = createPointerEvent('pointermove', toPoint);
+  const pointerUpEvent = createPointerEvent('pointerup', toPoint);
+
+  selectionOverlayElement.dispatchEvent(pointerDownEvent);
+  selectionOverlayElement.dispatchEvent(pointerMoveEvent);
+  selectionOverlayElement.dispatchEvent(pointerUpEvent);
+  return flushTasks();
+}
diff --git a/chrome/test/data/webui/new_tab_page/modules/modules.gni b/chrome/test/data/webui/new_tab_page/modules/modules.gni
index 2d75fca7..40c3de9 100644
--- a/chrome/test/data/webui/new_tab_page/modules/modules.gni
+++ b/chrome/test/data/webui/new_tab_page/modules/modules.gni
@@ -7,7 +7,7 @@
 import("./feed/feed.gni")
 import("./history_clusters/history_clusters.gni")
 import("./photos/photos.gni")
-import("./recipes/recipes.gni")
+import("./v2/calendar/calendar.gni")
 import("./v2/file_suggestion/file_suggestion.gni")
 import("./v2/history_clusters/history_clusters.gni")
 import("./v2/tab_resumption/tab_resumption.gni")
@@ -26,8 +26,8 @@
       "modules/modules_test.ts",
       "modules/v2/modules_test.ts",
       "modules/v2/module_header_test.ts",
-    ] + cart_test_files + drive_test_files + file_suggestion_v2_test_files +
-    feed_test_files + photos_test_files + recipes_test_files +
+    ] + cart_test_files + calendar_v2_test_files + drive_test_files +
+    file_suggestion_v2_test_files + feed_test_files + photos_test_files +
     history_clusters_test_files + history_clusters_v2_test_files +
     tab_resumption_test_files
 
diff --git a/chrome/test/data/webui/new_tab_page/modules/recipes/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/recipes/module_test.ts
deleted file mode 100644
index 96879b4..0000000
--- a/chrome/test/data/webui/new_tab_page/modules/recipes/module_test.ts
+++ /dev/null
@@ -1,476 +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 type {DismissModuleEvent, RecipesModuleElement} from 'chrome://new-tab-page/lazy_load.js';
-import {RecipesHandlerProxy, recipeTasksDescriptor} from 'chrome://new-tab-page/lazy_load.js';
-import type {CrAutoImgElement} from 'chrome://new-tab-page/new_tab_page.js';
-import {$$} from 'chrome://new-tab-page/new_tab_page.js';
-import {RecipesHandlerRemote} from 'chrome://new-tab-page/recipes.mojom-webui.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
-import type {TestMock} from 'chrome://webui-test/test_mock.js';
-import {eventToPromise} from 'chrome://webui-test/test_util.js';
-
-import {installMock} from '../../test_support.js';
-
-suite('NewTabPageModulesRecipesTest', () => {
-  let handler: TestMock<RecipesHandlerRemote>;
-
-  setup(() => {
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-
-    handler = installMock(RecipesHandlerRemote, RecipesHandlerProxy.setHandler);
-  });
-
-  test('creates no module if no task', async () => {
-    // Arrange.
-    handler.setResultFor('getPrimaryTask', Promise.resolve({task: null}));
-
-    // Act.
-    const moduleElement =
-        await recipeTasksDescriptor.initialize(0) as RecipesModuleElement;
-
-    // Assert.
-    assertEquals(1, handler.getCallCount('getPrimaryTask'));
-    assertEquals(null, moduleElement);
-  });
-
-  test('creates module if task', async () => {
-    // Arrange.
-    const task = {
-      title: 'Hello world',
-      recipes: [
-        {
-          name: 'foo',
-          imageUrl: {url: 'https://foo.com/img.png'},
-          siteName: 'Foo Site',
-          info: 'foo info',
-          targetUrl: {url: 'https://foo.com'},
-        },
-        {
-          name: 'bar',
-          imageUrl: {url: 'https://bar.com/img.png'},
-          siteName: 'Bar Site',
-          info: 'bar info',
-          targetUrl: {url: 'https://bar.com'},
-        },
-      ],
-      relatedSearches: [
-        {
-          text: 'baz',
-          targetUrl: {url: 'https://baz.com'},
-        },
-        {
-          text: 'blub',
-          targetUrl: {url: 'https://blub.com'},
-        },
-      ],
-    };
-    handler.setResultFor('getPrimaryTask', Promise.resolve({task}));
-
-    // Act.
-    const moduleElement =
-        await recipeTasksDescriptor.initialize(0) as RecipesModuleElement;
-    assertTrue(!!moduleElement);
-    document.body.append(moduleElement);
-    moduleElement.$.recipesRepeat.render();
-    moduleElement.$.relatedSearchesRepeat.render();
-
-    // Assert.
-    const recipes =
-        moduleElement.shadowRoot!.querySelectorAll<HTMLAnchorElement>(
-            '.recipe');
-    const pills =
-        moduleElement.shadowRoot!.querySelectorAll<HTMLAnchorElement>('.pill');
-    assertEquals(1, handler.getCallCount('getPrimaryTask'));
-    assertEquals(2, recipes.length);
-    assertEquals(2, pills.length);
-    assertEquals('https://foo.com/', recipes[0]!.href);
-    assertEquals(
-        'https://foo.com/img.png',
-        recipes[0]!.querySelector<CrAutoImgElement>('img')!.autoSrc);
-    assertEquals(
-        'Foo Site',
-        recipes[0]!.querySelector<HTMLElement>('.secondary')!.innerText);
-    assertEquals(
-        'foo info', recipes[0]!.querySelector<HTMLElement>('.tag')!.innerText);
-    assertEquals(
-        'foo', recipes[0]!.querySelector<HTMLElement>('.name')!.innerText);
-    assertEquals('foo', recipes[0]!.querySelector<HTMLElement>('.name')!.title);
-    assertEquals('https://bar.com/', recipes[1]!.href);
-    assertEquals(
-        'https://bar.com/img.png',
-        recipes[1]!.querySelector<CrAutoImgElement>('img')!.autoSrc);
-    assertEquals(
-        'Bar Site',
-        recipes[1]!.querySelector<HTMLElement>('.secondary')!.innerText);
-    assertEquals(
-        'bar info', recipes[1]!.querySelector<HTMLElement>('.tag')!.innerText);
-    assertEquals(
-        'bar', recipes[1]!.querySelector<HTMLElement>('.name')!.innerText);
-    assertEquals('bar', recipes[1]!.querySelector<HTMLElement>('.name')!.title);
-    assertEquals('https://baz.com/', pills[0]!.href);
-    assertEquals(
-        'baz', pills[0]!.querySelector<HTMLElement>('.search-text')!.innerText);
-    assertEquals('https://blub.com/', pills[1]!.href);
-    assertEquals(
-        'blub',
-        pills[1]!.querySelector<HTMLElement>('.search-text')!.innerText);
-  });
-
-  test('recipes and pills are not displayed when cutoff', async () => {
-    loadTimeData.overrideValues({
-      modulesOverflowScrollbarEnabled: true,
-      wideModulesEnabled: false,
-    });
-
-    const recipesCount = 3;
-    const relatedSearchesCount = 10;
-    const repeat = (n: number, fn: () => any) => Array(n).fill(0).map(fn);
-    handler.setResultFor('getPrimaryTask', Promise.resolve({
-      task: {
-        title: 'Hello world',
-        recipes:
-            repeat(recipesCount, () => ({
-                                   name: 'foo',
-                                   imageUrl: {url: 'https://foo.com/img.png'},
-                                   siteName: 'Foo Site',
-                                   targetUrl: {url: 'https://foo.com'},
-                                 })),
-        relatedSearches:
-            repeat(relatedSearchesCount, () => ({
-                                           text: 'baz',
-                                           targetUrl: {url: 'https://baz.com'},
-                                         })),
-      },
-    }));
-    const moduleElement =
-        await recipeTasksDescriptor.initialize(0) as RecipesModuleElement;
-    assertTrue(!!moduleElement);
-    document.body.append(moduleElement);
-    moduleElement.$.recipesRepeat.render();
-    moduleElement.$.relatedSearchesRepeat.render();
-
-    const getRecipeElements = () => Array.from(
-        moduleElement.shadowRoot!.querySelectorAll<HTMLAnchorElement>(
-            '.recipe'));
-    assertEquals(recipesCount, getRecipeElements().length);
-
-    const getPillElements = () => Array.from(
-        moduleElement.shadowRoot!.querySelectorAll<HTMLAnchorElement>('.pill'));
-    assertEquals(relatedSearchesCount, getPillElements().length);
-
-    const displayedPillCount = () =>
-        getPillElements().filter(el => el.style.display !== 'none').length;
-    const checkDisplayedPills = async (width: string, count: number) => {
-      const waitForVisibilityUpdate =
-          eventToPromise('visibility-update', moduleElement);
-      moduleElement.style.width = width;
-      await waitForVisibilityUpdate;
-      assertEquals(count, displayedPillCount());
-    };
-    await checkDisplayedPills('561px', 7);
-  });
-
-  test(
-      'recipes and pills are hidden when cutoff and no overflow scroll',
-      async () => {
-        loadTimeData.overrideValues({
-          modulesOverflowScrollbarEnabled: false,
-          wideModulesEnabled: false,
-        });
-
-        const recipesCount = 3;
-        const relatedSearchesCount = 10;
-        const repeat = (n: number, fn: () => any) => Array(n).fill(0).map(fn);
-        handler.setResultFor('getPrimaryTask', Promise.resolve({
-          task: {
-            title: 'Hello world',
-            recipes: repeat(
-                recipesCount, () => ({
-                                name: 'foo',
-                                imageUrl: {url: 'https://foo.com/img.png'},
-                                siteName: 'Foo Site',
-                                targetUrl: {url: 'https://foo.com'},
-                              })),
-            relatedSearches: repeat(
-                relatedSearchesCount, () => ({
-                                        text: 'baz',
-                                        targetUrl: {url: 'https://baz.com'},
-                                      })),
-          },
-        }));
-        const moduleElement =
-            await recipeTasksDescriptor.initialize(0) as RecipesModuleElement;
-        assertTrue(!!moduleElement);
-        document.body.append(moduleElement);
-        moduleElement.$.recipesRepeat.render();
-        moduleElement.$.relatedSearchesRepeat.render();
-
-        const getRecipeElements = () => Array.from(
-            moduleElement.shadowRoot!.querySelectorAll<HTMLAnchorElement>(
-                '.recipe'));
-        assertEquals(3, getRecipeElements().length);
-
-        const getPillElements = () => Array.from(
-            moduleElement.shadowRoot!.querySelectorAll<HTMLAnchorElement>(
-                '.pill'));
-        assertEquals(relatedSearchesCount, getPillElements().length);
-
-        const hiddenPillCount = () =>
-            getPillElements()
-                .filter(el => el.style.visibility === 'hidden')
-                .length;
-        const checkHiddenPills = async (width: string, count: number) => {
-          const waitForVisibilityUpdate =
-              eventToPromise('visibility-update', moduleElement);
-          moduleElement.style.width = width;
-          await waitForVisibilityUpdate;
-          assertEquals(count, hiddenPillCount());
-        };
-        await checkHiddenPills('561px', 3);
-        await checkHiddenPills('337px', 6);
-      });
-
-  test('Backend is notified when module is dismissed or restored', async () => {
-    // Arrange.
-    const task = {
-      title: 'Continue searching for Hello world',
-      name: 'Hello world',
-      recipes: [
-        {
-          name: 'foo',
-          imageUrl: {url: 'https://foo.com/img.png'},
-          siteName: 'Foo Site',
-          targetUrl: {url: 'https://foo.com'},
-        },
-        {
-          name: 'bar',
-          imageUrl: {url: 'https://bar.com/img.png'},
-          siteName: 'Bar Site',
-          targetUrl: {url: 'https://bar.com'},
-        },
-      ],
-      relatedSearches: [
-        {
-          text: 'baz',
-          targetUrl: {url: 'https://baz.com'},
-        },
-        {
-          text: 'blub',
-          targetUrl: {url: 'https://blub.com'},
-        },
-      ],
-    };
-    handler.setResultFor('getPrimaryTask', Promise.resolve({task}));
-
-    // Arrange.
-    const moduleElement =
-        await recipeTasksDescriptor.initialize(0) as RecipesModuleElement;
-    assertTrue(!!moduleElement);
-    document.body.append(moduleElement);
-    await flushTasks();
-
-    // Act.
-    const waitForDismissEvent = eventToPromise('dismiss-module', moduleElement);
-    const dismissButton =
-        moduleElement.shadowRoot!.querySelector('ntp-module-header')!
-            .shadowRoot!.querySelector<HTMLElement>('#dismissButton')!;
-    dismissButton.click();
-
-    // Assert.
-    const dismissEvent: DismissModuleEvent = await waitForDismissEvent;
-    const toastMessage = dismissEvent.detail.message;
-    const moduleHeaderTitle =
-        moduleElement.shadowRoot!.querySelector(
-                                     'ntp-module-header')!.textContent!.trim();
-    assertEquals(moduleHeaderTitle + ' hidden', toastMessage);
-    assertTrue(!!dismissEvent.detail.restoreCallback);
-    assertEquals('Hello world', await handler.whenCalled('dismissTask'));
-
-    // Act.
-    const restoreCallback = dismissEvent.detail.restoreCallback!;
-    restoreCallback();
-
-    // Assert.
-    assertEquals('Hello world', await handler.whenCalled('restoreTask'));
-  });
-
-  test('info button click opens info dialog', async () => {
-    // Arrange.
-    const task = {
-      title: '',
-      recipes: [],
-      relatedSearches: [],
-    };
-    handler.setResultFor('getPrimaryTask', Promise.resolve({task}));
-    const moduleElement =
-        await recipeTasksDescriptor.initialize(0) as RecipesModuleElement;
-    assertTrue(!!moduleElement);
-    document.body.append(moduleElement);
-
-    // Act.
-    ($$(moduleElement, 'ntp-module-header')!
-     ).dispatchEvent(new Event('info-button-click'));
-
-    // Assert.
-    assertTrue(!!$$(moduleElement, 'ntp-info-dialog'));
-  });
-
-  [true, false].forEach(historicalArm => {
-    test(
-        `change text for historical experiment arm ${historicalArm}`,
-        async () => {
-          // Arrange.
-          loadTimeData.overrideValues({
-            modulesRecipeHistoricalExperimentEnabled: historicalArm,
-          });
-
-          const task = {
-            title: 'Hello world',
-            recipes: [
-              {
-                name: 'foo',
-                imageUrl: {url: 'https://foo.com/img.png'},
-                siteName: 'Foo Site',
-                info: 'foo info',
-                targetUrl: {url: 'https://foo.com'},
-              },
-              {
-                name: 'bar',
-                imageUrl: {url: 'https://bar.com/img.png'},
-                siteName: 'Bar Site',
-                info: 'bar info',
-                targetUrl: {url: 'https://bar.com'},
-              },
-            ],
-          };
-          handler.setResultFor('getPrimaryTask', Promise.resolve({task}));
-
-          // Act.
-          const moduleElement =
-              await recipeTasksDescriptor.initialize(0) as RecipesModuleElement;
-          assertTrue(!!moduleElement);
-          document.body.append(moduleElement);
-          moduleElement.$.recipesRepeat.render();
-          moduleElement.$.relatedSearchesRepeat.render();
-
-          const headerElement =
-              moduleElement.shadowRoot!.querySelector('ntp-module-header')!;
-          const menuElement =
-              headerElement.shadowRoot!.querySelector('#actionMenu')!;
-          const dismissButton =
-              menuElement.querySelector<HTMLElement>('#dismissButton')!;
-          const disableButton =
-              menuElement.querySelector<HTMLElement>('#disableButton')!;
-
-          // Assert.
-          // check title
-          const title = historicalArm ?
-              loadTimeData.getString('modulesRecipeViewedTasksSentence') :
-              loadTimeData.getString('modulesRecipeTasksSentence');
-          assertEquals(title, headerElement.innerText);
-
-          // check menu hide text
-          const hideText = historicalArm ?
-              loadTimeData.getString('modulesRecipeViewedTasksLowerThese') :
-              loadTimeData.getString('modulesRecipeTasksLowerThese');
-          assertTrue(dismissButton.innerText.includes(hideText));
-
-          // check menu don't show texts
-          const showText = historicalArm ?
-              loadTimeData.getString('modulesRecipeViewedTasksLower') :
-              loadTimeData.getString('modulesRecipeTasksLower');
-          assertTrue(disableButton.innerText.includes(showText));
-        });
-  });
-
-  test('hide query chip container when relatedSearches is empty', async () => {
-    // Arrange.
-    const task = {
-      title: 'Hello world',
-      recipes: [
-        {
-          name: 'foo',
-          imageUrl: {url: 'https://foo.com/img.png'},
-          siteName: 'Foo Site',
-          info: 'foo info',
-          targetUrl: {url: 'https://foo.com'},
-        },
-        {
-          name: 'bar',
-          imageUrl: {url: 'https://bar.com/img.png'},
-          siteName: 'Bar Site',
-          info: 'bar info',
-          targetUrl: {url: 'https://bar.com'},
-        },
-      ],
-      relatedSearches: [],
-    };
-    handler.setResultFor('getPrimaryTask', Promise.resolve({task}));
-
-    // Act.
-    const moduleElement =
-        await recipeTasksDescriptor.initialize(0) as RecipesModuleElement;
-    document.body.append(moduleElement);
-    moduleElement.$.recipesRepeat.render();
-    moduleElement.$.relatedSearchesRepeat.render();
-
-    // Assert.
-    const relatedSearchesContainer =
-        moduleElement.shadowRoot!.querySelector<HTMLAnchorElement>(
-            '#relatedSearches');
-    assertTrue(relatedSearchesContainer!.hidden);
-  });
-
-  test(
-      'show query chip container when relatedSearches is not empty',
-      async () => {
-        // Arrange.
-        const task = {
-          title: 'Hello world',
-          recipes: [
-            {
-              name: 'foo',
-              imageUrl: {url: 'https://foo.com/img.png'},
-              siteName: 'Foo Site',
-              info: 'foo info',
-              targetUrl: {url: 'https://foo.com'},
-            },
-            {
-              name: 'bar',
-              imageUrl: {url: 'https://bar.com/img.png'},
-              siteName: 'Bar Site',
-              info: 'bar info',
-              targetUrl: {url: 'https://bar.com'},
-            },
-          ],
-          relatedSearches: [
-            {
-              text: 'baz',
-              targetUrl: {url: 'https://baz.com'},
-            },
-            {
-              text: 'blub',
-              targetUrl: {url: 'https://blub.com'},
-            },
-          ],
-        };
-        handler.setResultFor('getPrimaryTask', Promise.resolve({task}));
-
-        // Act.
-        const moduleElement =
-            await recipeTasksDescriptor.initialize(0) as RecipesModuleElement;
-        document.body.append(moduleElement);
-        moduleElement.$.recipesRepeat.render();
-        moduleElement.$.relatedSearchesRepeat.render();
-
-        // Assert.
-        const relatedSearchesContainer =
-            moduleElement.shadowRoot!.querySelector<HTMLAnchorElement>(
-                '#relatedSearches');
-        assertFalse(relatedSearchesContainer!.hidden);
-      });
-});
diff --git a/chrome/test/data/webui/new_tab_page/modules/recipes/recipes.gni b/chrome/test/data/webui/new_tab_page/modules/recipes/recipes.gni
deleted file mode 100644
index 0ef5cd0f..0000000
--- a/chrome/test/data/webui/new_tab_page/modules/recipes/recipes.gni
+++ /dev/null
@@ -1,5 +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.
-
-recipes_test_files = [ "modules/recipes/module_test.ts" ]
diff --git a/chrome/test/data/webui/new_tab_page/modules/v2/calendar/calendar.gni b/chrome/test/data/webui/new_tab_page/modules/v2/calendar/calendar.gni
new file mode 100644
index 0000000..ed270af
--- /dev/null
+++ b/chrome/test/data/webui/new_tab_page/modules/v2/calendar/calendar.gni
@@ -0,0 +1,5 @@
+# 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.
+
+calendar_v2_test_files = [ "modules/v2/calendar/module_test.ts" ]
diff --git a/chrome/test/data/webui/new_tab_page/modules/v2/calendar/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/v2/calendar/module_test.ts
new file mode 100644
index 0000000..7c520a8
--- /dev/null
+++ b/chrome/test/data/webui/new_tab_page/modules/v2/calendar/module_test.ts
@@ -0,0 +1,27 @@
+// 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 type {CalendarModuleElement} from 'chrome://new-tab-page/lazy_load.js';
+import {googleCalendarDescriptor} from 'chrome://new-tab-page/lazy_load.js';
+import {assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
+import {isVisible} from 'chrome://webui-test/test_util.js';
+
+suite('NewTabPageModulesCalendarModuleTest', () => {
+  setup(() => {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+  });
+
+  test('creates module', async () => {
+    const module =
+        await googleCalendarDescriptor.initialize(0) as CalendarModuleElement;
+    assertTrue(!!module);
+    document.body.append(module);
+    await waitAfterNextRender(module);
+
+    // Assert.
+    assertTrue(
+        isVisible(module.shadowRoot!.querySelector('ntp-module-header-v2')));
+  });
+});
diff --git a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc
index 9429954e..60aabb67 100644
--- a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc
+++ b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc
@@ -126,6 +126,10 @@
 }
 #endif  // !defined(OFFICIAL_BUILD)
 
+IN_PROC_BROWSER_TEST_F(NewTabPageModulesTest, CalendarModule) {
+  RunTest("new_tab_page/modules/v2/calendar/module_test.js", "mocha.run()");
+}
+
 IN_PROC_BROWSER_TEST_F(NewTabPageModulesTest, DriveModule) {
   RunTest("new_tab_page/modules/drive/module_test.js", "mocha.run()");
 }
@@ -135,10 +139,6 @@
           "mocha.run()");
 }
 
-IN_PROC_BROWSER_TEST_F(NewTabPageModulesTest, RecipesModule) {
-  RunTest("new_tab_page/modules/recipes/module_test.js", "mocha.run()");
-}
-
 // TODO(crbug.com/1485080): Fails on Linux Debug bots.
 #if BUILDFLAG(IS_LINUX) && !defined(NDEBUG)
 #define MAYBE_ChromeCartModule DISABLED_ChromeCartModule
diff --git a/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_test.ts
index 11eb299..0c6a9df 100644
--- a/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_test.ts
@@ -237,6 +237,7 @@
 
   function getDefaultGlobalPolicy(): GlobalPolicy {
     return {
+      allowApnModification: false,
       allowOnlyPolicyWifiNetworksToConnect: false,
       allowCellularSimLock: false,
       allowCellularHotspot: false,
diff --git a/chrome/test/data/webui/settings/chromeos/internet_page/internet_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/internet_page/internet_subpage_test.ts
index 420821b..0273d7c 100644
--- a/chrome/test/data/webui/settings/chromeos/internet_page/internet_subpage_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/internet_page/internet_subpage_test.ts
@@ -243,6 +243,7 @@
           InhibitReason.kNotInhibited, simInfos);
 
       cellularNetworkList.globalPolicy = {
+        allowApnModification: false,
         allowOnlyPolicyWifiNetworksToConnect: false,
         allowCellularSimLock: false,
         allowCellularHotspot: false,
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_menu/os_settings_menu_revamp_test.ts b/chrome/test/data/webui/settings/chromeos/os_settings_menu/os_settings_menu_revamp_test.ts
index d8d8a67..f3fec5b08 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_menu/os_settings_menu_revamp_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_menu/os_settings_menu_revamp_test.ts
@@ -1175,6 +1175,24 @@
         });
   });
 
+  suite('Personalization menu item', () => {
+    function getPersonalizationMenuItem(): OsSettingsMenuItemElement {
+      const menuItem =
+          queryMenuItemByPath(`/${routesMojom.PERSONALIZATION_SECTION_PATH}`);
+      assertTrue(!!menuItem);
+      return menuItem;
+    }
+
+    test('Description reflects load time string', async () => {
+      await createMenu();
+
+      const menuItem = getPersonalizationMenuItem();
+      assertEquals(
+          settingsMenu.i18n('personalizationMenuItemDescription'),
+          menuItem.sublabel);
+    });
+  });
+
   suite('Privacy menu item', () => {
     test('Privacy menu item description', async () => {
       await createMenu();
diff --git a/chrome/test/data/webui/settings/cookies_page_test.ts b/chrome/test/data/webui/settings/cookies_page_test.ts
index 4312dd5..db3effc 100644
--- a/chrome/test/data/webui/settings/cookies_page_test.ts
+++ b/chrome/test/data/webui/settings/cookies_page_test.ts
@@ -96,6 +96,7 @@
     // By default these toggles should be hidden.
     assertFalse(isChildVisible(page, '#blockThirdPartyToggle'));
     assertFalse(isChildVisible(page, '#ipProtectionToggle'));
+    assertFalse(isChildVisible(page, '#fingerprintingProtectionToggle'));
   });
 
   test('ThirdPartyCookiesRadioClicksRecorded', async function() {
@@ -487,6 +488,55 @@
   });
 });
 
+suite('FingerprintingProtectionToggle', function() {
+  let page: SettingsCookiesPageElement;
+  let settingsPrefs: SettingsPrefsElement;
+  let testMetricsBrowserProxy: TestMetricsBrowserProxy;
+
+  suiteSetup(function() {
+    loadTimeData.overrideValues({
+      isFingerprintingProtectionEnabled: true,
+    });
+    settingsPrefs = document.createElement('settings-prefs');
+    return CrSettingsPrefs.initialized;
+  });
+
+  setup(function() {
+    testMetricsBrowserProxy = new TestMetricsBrowserProxy();
+    MetricsBrowserProxyImpl.setInstance(testMetricsBrowserProxy);
+
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    page = document.createElement('settings-cookies-page');
+    page.prefs = settingsPrefs.prefs!;
+    document.body.appendChild(page);
+    flush();
+  });
+
+  test('CheckVisibility', function() {
+    // Setting is visible
+    assertTrue(isChildVisible(page, '#fingerprintingProtectionToggle'));
+  });
+
+  test('ToggleFingerprintingProtection', async function() {
+    page.set(
+        'prefs.tracking_protection.fingerprinting_protection_enabled.value',
+        false);
+    const fingerprintingProtectionToggle =
+        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+            '#fingerprintingProtectionToggle')!;
+    assertTrue(!!fingerprintingProtectionToggle);
+
+    fingerprintingProtectionToggle.click();
+    const result =
+        await testMetricsBrowserProxy.whenCalled('recordSettingsPageHistogram');
+    assertEquals(PrivacyElementInteractions.FINGERPRINTING_PROTECTION, result);
+    assertEquals(
+        page.getPref(
+            'tracking_protection.fingerprinting_protection_enabled.value'),
+        true);
+  });
+});
+
 suite('TrackingProtectionSettingsRollbackNotice', function() {
   let page: SettingsCookiesPageElement;
   let settingsPrefs: SettingsPrefsElement;
diff --git a/chrome/test/data/webui/settings/privacy_page_test.ts b/chrome/test/data/webui/settings/privacy_page_test.ts
index 64be8ac..c02e2ac 100644
--- a/chrome/test/data/webui/settings/privacy_page_test.ts
+++ b/chrome/test/data/webui/settings/privacy_page_test.ts
@@ -279,7 +279,6 @@
     Router.getInstance().navigateTo(routes.SITE_SETTINGS_AUTOMATIC_FULLSCREEN);
     await flushTasks();
 
-    assertTrue(isChildVisible(page, '#automaticFullscreenBlock'));
     const categorySettingExceptions =
         page.shadowRoot!.querySelector('category-setting-exceptions');
     assertTrue(!!categorySettingExceptions);
diff --git a/chrome/test/data/webui/settings/settings_browsertest.cc b/chrome/test/data/webui/settings/settings_browsertest.cc
index 10832b7..ab098ca5 100644
--- a/chrome/test/data/webui/settings/settings_browsertest.cc
+++ b/chrome/test/data/webui/settings/settings_browsertest.cc
@@ -603,6 +603,12 @@
           "runMochaSuite('IpProtectionToggle')");
 }
 
+IN_PROC_BROWSER_TEST_F(SettingsCookiesPageTest,
+                       FingerprintingProtectionToggle) {
+  RunTest("settings/cookies_page_test.js",
+          "runMochaSuite('FingerprintingProtectionToggle')");
+}
+
 IN_PROC_BROWSER_TEST_F(SettingsCookiesPageTest, TrackingProtectionSettings) {
   RunTest("settings/cookies_page_test.js",
           "runMochaSuite('TrackingProtectionSettings')");
diff --git a/chrome/test/data/webui/side_panel/read_anything/color_menu_test.ts b/chrome/test/data/webui/side_panel/read_anything/color_menu_test.ts
index 7c25343a..56aa792 100644
--- a/chrome/test/data/webui/side_panel/read_anything/color_menu_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/color_menu_test.ts
@@ -11,7 +11,7 @@
 import type {ReadAnythingToolbarElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything_toolbar.js';
 import {assertEquals, assertGT, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
 
-import {suppressInnocuousErrors} from './common.js';
+import {getItemsInMenu, stubAnimationFrame, suppressInnocuousErrors} from './common.js';
 import {FakeReadingMode} from './fake_reading_mode.js';
 import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';
 
@@ -37,9 +37,13 @@
   });
 
   test('is dropdown menu', () => {
+    stubAnimationFrame();
     const menuButton =
         toolbar.shadowRoot!.querySelector<CrIconButtonElement>('#color');
+
     menuButton!.click();
+    flush();
+
     assertTrue(toolbar.$.colorMenu.get().open);
   });
 
@@ -47,9 +51,7 @@
     let colorMenuOptions: HTMLButtonElement[];
 
     setup(() => {
-      colorMenuOptions = Array.from(
-          toolbar.$.colorMenu.get().querySelectorAll<HTMLButtonElement>(
-              '.dropdown-item'));
+      colorMenuOptions = getItemsInMenu(toolbar.$.colorMenu);
     });
 
     test('option click propagates change', () => {
diff --git a/chrome/test/data/webui/side_panel/read_anything/common.ts b/chrome/test/data/webui/side_panel/read_anything/common.ts
index 748c058..f65c78d 100644
--- a/chrome/test/data/webui/side_panel/read_anything/common.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/common.ts
@@ -1,6 +1,8 @@
 // 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 type {CrActionMenuElement} from '//resources/cr_elements/cr_action_menu/cr_action_menu.js';
+import type {CrLazyRenderElement} from '//resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
 import {flush} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import type {ReadAnythingElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/app.js';
 
@@ -29,3 +31,21 @@
     }
   };
 }
+
+// Runs the requestAnimationFrame callback immediately
+export function stubAnimationFrame() {
+  window.requestAnimationFrame = (callback) => {
+    callback(0);
+    return 0;
+  };
+}
+
+// Returns the list of items in the given dropdown menu
+export function getItemsInMenu(
+    lazyMenu: CrLazyRenderElement<CrActionMenuElement>): HTMLButtonElement[] {
+  // We need to call menu.get here to ensure the menu has rendered before we
+  // query the dropdown item elements.
+  const menu = lazyMenu.get();
+  flush();
+  return Array.from(menu.querySelectorAll<HTMLButtonElement>('.dropdown-item'));
+}
diff --git a/chrome/test/data/webui/side_panel/read_anything/font_menu_test.ts b/chrome/test/data/webui/side_panel/read_anything/font_menu_test.ts
index ccb8598..c407946 100644
--- a/chrome/test/data/webui/side_panel/read_anything/font_menu_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/font_menu_test.ts
@@ -11,7 +11,7 @@
 import type {ReadAnythingToolbarElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything_toolbar.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
 
-import {suppressInnocuousErrors} from './common.js';
+import {getItemsInMenu, stubAnimationFrame, suppressInnocuousErrors} from './common.js';
 import {FakeReadingMode} from './fake_reading_mode.js';
 import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';
 
@@ -63,17 +63,15 @@
     function updateFonts(supportedFonts: string[]): void {
       chrome.readingMode.supportedFonts = supportedFonts;
       toolbar.updateFonts();
-      // We need an extra call to fontMenu.get here just to ensure the
-      // menu has rendered before we query the dropdown item elements.
-      toolbar.$.fontMenu.get();
-      flush();
-      fontMenuOptions = Array.from(
-          toolbar.$.fontMenu.get().querySelectorAll<HTMLButtonElement>(
-              '.dropdown-item'));
+      fontMenuOptions = getItemsInMenu(toolbar.$.fontMenu);
     }
 
     test('is dropdown menu', () => {
+      stubAnimationFrame();
+
       menuButton!.click();
+      flush();
+
       assertTrue(toolbar.$.fontMenu.get().open);
     });
 
@@ -84,6 +82,10 @@
       updateFonts(['font 1']);
       assertEquals(fontMenuOptions.length, 1);
 
+      // initial-count in the dom-repeat for the fonts menu limits the
+      // size of the font menu, so adding more than 8 fonts is difficult to
+      // test. If more than 8 fonts are added on the actual menu, we can
+      // increase the initial-count.
       updateFonts([
         'font 1',
         'font 2',
@@ -93,10 +95,8 @@
         'font 6',
         'font 7',
         'font 8',
-        'font 9',
-        'font 10',
       ]);
-      assertEquals(fontMenuOptions.length, 10);
+      assertEquals(fontMenuOptions.length, 8);
     });
 
     test('each font option is styled with the font that it is', () => {
diff --git a/chrome/test/data/webui/side_panel/read_anything/font_size_test.ts b/chrome/test/data/webui/side_panel/read_anything/font_size_test.ts
index c1b81f8..f7afa48 100644
--- a/chrome/test/data/webui/side_panel/read_anything/font_size_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/font_size_test.ts
@@ -10,7 +10,7 @@
 import type {ReadAnythingToolbarElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything_toolbar.js';
 import {assertEquals, assertFalse, assertGT, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
 
-import {suppressInnocuousErrors} from './common.js';
+import {stubAnimationFrame, suppressInnocuousErrors} from './common.js';
 import {FakeReadingMode} from './fake_reading_mode.js';
 
 suite('FontSize', () => {
@@ -46,13 +46,16 @@
     });
 
     test('is dropdown menu', () => {
+      stubAnimationFrame();
+
       menuButton!.click();
+      flush();
+
       assertTrue(toolbar.$.fontSizeMenu.get().open);
     });
 
     test('increase clicked increases container font size', () => {
       const startingFontSize = chrome.readingMode.fontSize;
-      menuButton!.click();
 
       toolbar.$.fontSizeMenu.get()
           .querySelector<CrIconButtonElement>('#font-size-increase')!.click();
@@ -63,7 +66,6 @@
 
     test('decrease clicked decreases container font size', () => {
       const startingFontSize = chrome.readingMode.fontSize;
-      menuButton!.click();
 
       toolbar.$.fontSizeMenu.get()
           .querySelector<CrIconButtonElement>('#font-size-decrease')!.click();
@@ -74,7 +76,6 @@
 
     test('reset clicked returns font size to starting size', () => {
       const startingFontSize = chrome.readingMode.fontSize;
-      menuButton!.click();
 
       toolbar.$.fontSizeMenu.get()
           .querySelector<CrIconButtonElement>('#font-size-increase')!.click();
diff --git a/chrome/test/data/webui/side_panel/read_anything/letter_spacing_test.ts b/chrome/test/data/webui/side_panel/read_anything/letter_spacing_test.ts
index d18d995..6206500 100644
--- a/chrome/test/data/webui/side_panel/read_anything/letter_spacing_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/letter_spacing_test.ts
@@ -11,7 +11,7 @@
 import type {ReadAnythingToolbarElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything_toolbar.js';
 import {assertEquals, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
 
-import {suppressInnocuousErrors} from './common.js';
+import {getItemsInMenu, stubAnimationFrame, suppressInnocuousErrors} from './common.js';
 import {FakeReadingMode} from './fake_reading_mode.js';
 import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';
 
@@ -34,13 +34,16 @@
     toolbar = document.createElement('read-anything-toolbar');
     document.body.appendChild(toolbar);
     flush();
-    const menuButton = toolbar.shadowRoot!.querySelector<CrIconButtonElement>(
-        '#letter-spacing');
-    menuButton!.click();
-    flush();
   });
 
   test('is dropdown menu', () => {
+    stubAnimationFrame();
+    const menuButton = toolbar.shadowRoot!.querySelector<CrIconButtonElement>(
+        '#letter-spacing');
+
+    menuButton!.click();
+    flush();
+
     assertTrue(toolbar.$.letterSpacingMenu.get().open);
   });
 
@@ -48,9 +51,7 @@
     let letterSpacingMenuOptions: HTMLButtonElement[];
 
     setup(() => {
-      letterSpacingMenuOptions = Array.from(
-          toolbar.$.letterSpacingMenu.get().querySelectorAll<HTMLButtonElement>(
-              '.dropdown-item'));
+      letterSpacingMenuOptions = getItemsInMenu(toolbar.$.letterSpacingMenu);
     });
 
     test('has 3 options', () => {
diff --git a/chrome/test/data/webui/side_panel/read_anything/line_spacing_test.ts b/chrome/test/data/webui/side_panel/read_anything/line_spacing_test.ts
index f419527..00d50468 100644
--- a/chrome/test/data/webui/side_panel/read_anything/line_spacing_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/line_spacing_test.ts
@@ -11,7 +11,7 @@
 import type {ReadAnythingToolbarElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything_toolbar.js';
 import {assertEquals, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
 
-import {suppressInnocuousErrors} from './common.js';
+import {getItemsInMenu, stubAnimationFrame, suppressInnocuousErrors} from './common.js';
 import {FakeReadingMode} from './fake_reading_mode.js';
 import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';
 
@@ -34,13 +34,16 @@
     toolbar = document.createElement('read-anything-toolbar');
     document.body.appendChild(toolbar);
     flush();
-    const menuButton =
-        toolbar.shadowRoot!.querySelector<CrIconButtonElement>('#line-spacing');
-    menuButton!.click();
-    flush();
   });
 
   test('is dropdown menu', () => {
+    stubAnimationFrame();
+    const menuButton =
+        toolbar.shadowRoot!.querySelector<CrIconButtonElement>('#line-spacing');
+
+    menuButton!.click();
+    flush();
+
     assertTrue(toolbar.$.lineSpacingMenu.get().open);
   });
 
@@ -48,9 +51,7 @@
     let lineSpacingMenuOptions: HTMLButtonElement[];
 
     setup(() => {
-      lineSpacingMenuOptions = Array.from(
-          toolbar.$.lineSpacingMenu.get().querySelectorAll<HTMLButtonElement>(
-              '.dropdown-item'));
+      lineSpacingMenuOptions = getItemsInMenu(toolbar.$.lineSpacingMenu);
     });
 
     test('has 3 options', () => {
diff --git a/chrome/test/data/webui/side_panel/read_anything/rate_selection_test.ts b/chrome/test/data/webui/side_panel/read_anything/rate_selection_test.ts
index c447efd..19a76fa 100644
--- a/chrome/test/data/webui/side_panel/read_anything/rate_selection_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/rate_selection_test.ts
@@ -10,7 +10,7 @@
 import type {ReadAnythingToolbarElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything_toolbar.js';
 import {assertEquals, assertFalse, assertGT, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
 
-import {suppressInnocuousErrors} from './common.js';
+import {getItemsInMenu, stubAnimationFrame, suppressInnocuousErrors} from './common.js';
 import {FakeReadingMode} from './fake_reading_mode.js';
 import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';
 
@@ -51,19 +51,20 @@
     });
   });
 
-  suite('on menu button click', () => {
+  test('menu button opens menu', () => {
+    stubAnimationFrame();
+
+    rateButton.click();
+    flush();
+
+    assertTrue(toolbar.$.rateMenu.get().open);
+  });
+
+  suite('dropdown menu', () => {
     let options: HTMLButtonElement[];
 
     setup(() => {
-      rateButton.click();
-      flush();
-      options = Array.from(
-          toolbar.$.rateMenu.get().querySelectorAll<HTMLButtonElement>(
-              '.dropdown-item'));
-    });
-
-    test('opens menu', () => {
-      assertTrue(toolbar.$.rateMenu.get().open);
+      options = getItemsInMenu(toolbar.$.rateMenu);
     });
 
     test('has multiple options', () => {
@@ -80,7 +81,7 @@
       });
     });
 
-    suite(', then option click', () => {
+    suite('on option click', () => {
       let menuOption: HTMLButtonElement;
       let rateValue: number;
 
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection.ts b/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection.ts
index 7da644d5..ddfb2a7 100644
--- a/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection.ts
@@ -133,10 +133,12 @@
       assertFalse(app.speechPlayingState.paused);
       assertTrue(app.speechPlayingState.speechStarted);
       // The expected HTML with the current highlights.
-      const expected =
-          '<div><p><span><span class="current-read-highlight">World</span>' +
-          '</span></p><p><span><span class="current-read-highlight">Friend' +
-          '</span></span><span><span class="current-read-highlight">!</span>' +
+      const expected = '<div><p><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">World</span>' +
+          '</span></p><p><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">Friend' +
+          '</span></span><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">!</span>' +
           '</span></p></div>';
       const innerHTML = app.$.container.innerHTML;
       assertEquals(innerHTML, expected);
@@ -170,10 +172,12 @@
       assertTrue(app.speechPlayingState.paused);
       assertTrue(app.speechPlayingState.speechStarted);
       // The expected HTML with the current highlights.
-      const expected =
-          '<div><p><span><span class="current-read-highlight">World</span>' +
-          '</span></p><p><span><span class="current-read-highlight">Friend' +
-          '</span></span><span><span class="current-read-highlight">!</span>' +
+      const expected = '<div><p><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">World</span>' +
+          '</span></p><p><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">Friend' +
+          '</span></span><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">!</span>' +
           '</span></p></div>';
       const innerHTML = app.$.container.innerHTML;
       assertEquals(innerHTML, expected);
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_pdf.ts b/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_pdf.ts
index d9e66a2..c14c5236 100644
--- a/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_pdf.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_pdf.ts
@@ -136,10 +136,12 @@
       assertFalse(app.speechPlayingState.paused);
       assertTrue(app.speechPlayingState.speechStarted);
       // The expected HTML with the current highlights.
-      const expected =
-          '<div><p><span><span class="current-read-highlight">World</span>' +
-          '</span></p><p><span><span class="current-read-highlight">Friend' +
-          '</span></span><span><span class="current-read-highlight">!</span>' +
+      const expected = '<div><p><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">World</span>' +
+          '</span></p><p><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">Friend' +
+          '</span></span><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">!</span>' +
           '</span></p></div>';
       const innerHTML = app.$.container.innerHTML;
       assertEquals(innerHTML, expected);
@@ -173,10 +175,12 @@
       assertTrue(app.speechPlayingState.paused);
       assertTrue(app.speechPlayingState.speechStarted);
       // The expected HTML with the current highlights.
-      const expected =
-          '<div><p><span><span class="current-read-highlight">World</span>' +
-          '</span></p><p><span><span class="current-read-highlight">Friend' +
-          '</span></span><span><span class="current-read-highlight">!</span>' +
+      const expected = '<div><p><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">World</span>' +
+          '</span></p><p><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">Friend' +
+          '</span></span><span class="parent-of-highlight">' +
+          '<span class="current-read-highlight">!</span>' +
           '</span></p></div>';
       const innerHTML = app.$.container.innerHTML;
       assertEquals(innerHTML, expected);
diff --git a/chrome/test/data/webui/side_panel/read_anything/update_content_selection_with_highlights.ts b/chrome/test/data/webui/side_panel/read_anything/update_content_selection_with_highlights.ts
index 59e73b2..29f91c7 100644
--- a/chrome/test/data/webui/side_panel/read_anything/update_content_selection_with_highlights.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/update_content_selection_with_highlights.ts
@@ -5,7 +5,9 @@
 
 import {BrowserProxy} from '//resources/cr_components/color_change_listener/browser_proxy.js';
 import type {ReadAnythingElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/app.js';
+import {currentReadHighlightClass, previousReadHighlightClass} from 'chrome-untrusted://read-anything-side-panel.top-chrome/app.js';
 import {assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome-untrusted://webui-test/polymer_test_util.js';
 
 import {suppressInnocuousErrors} from './common.js';
 import {FakeReadingMode} from './fake_reading_mode.js';
@@ -31,10 +33,10 @@
 
   const textNodeIds = [3, 5, 7, 9];
   const texts = [
-    'in the jungle,',
-    'the mighty jungle, the lion sleeps tonight',
-    'in the jungle, the quiet jungle, the lion sleeps tonight',
-    'uyimbube, uyimbube',
+    'From the day we arrive on the planet',
+    'And blinking, step into the sun',
+    'There\'s more to see than can ever be seen, more to do than can ever be done',
+    'In the circle of life',
   ];
 
   setup(() => {
@@ -47,7 +49,6 @@
 
     app = document.createElement('read-anything-app');
     document.body.appendChild(app);
-    document.onselectionchange = () => {};
 
     assertEquals(textNodeIds.length, 4);
     assertEquals(texts.length, 4);
@@ -99,10 +100,268 @@
 
     // highlight given nodes
     fakeTree.setReadingHighlight(fromId, fromOffset, toId, toOffset);
-    app.highlightNodes([fromId, toId]);
+    const nodeIds = [fromId];
+    if (toId !== fromId) {
+      nodeIds.push(toId);
+    }
+    app.highlightNodes(nodeIds);
   }
 
+  suite('main panel selection is correct when selecting in the app: ', () => {
+    const previousSelector = '.' + previousReadHighlightClass;
+    const currentSelector = '.' + currentReadHighlightClass;
+    const afterSelector = 'p';
+
+    let selection: Selection;
+    let actualAnchorId: number;
+    let actualFocusId: number;
+    let actualAnchorOffset: number;
+    let actualFocusOffset: number;
+
+    // Looks for the node containing the given text inside the given selectors
+    function getTextNode(selector: string, text: string): Node {
+      const nodesToCheck =
+          Array.from(app.shadowRoot!.querySelectorAll(selector));
+      const parentNodeWithText =
+          nodesToCheck.find((element) => element.textContent!.includes(text));
+      return parentNodeWithText!.firstChild!;
+    }
+
+    // Sets the reading mode selection
+    async function selectNodes(
+        selector: string, anchorOffset: number, fullAnchorText: string,
+        focusOffset: number, fullFocusText?: string): Promise<void> {
+      const anchorText = fullAnchorText.slice(anchorOffset);
+      const anchorNode = getTextNode(selector, anchorText);
+      const focusText =
+          fullFocusText ? fullFocusText.slice(0, focusOffset) : anchorText;
+      const focusNode =
+          fullFocusText ? getTextNode(selector, focusText) : anchorNode;
+      selection.setBaseAndExtent(
+          anchorNode, anchorOffset, focusNode, focusOffset);
+      return flushTasks();
+    }
+
+    setup(() => {
+      selection = app.getSelection();
+      actualAnchorId = -1;
+      actualFocusId = -1;
+      actualAnchorOffset = -1;
+      actualFocusOffset = -1;
+
+      // Capture what's sent off to the main panel so we can verify
+      chrome.readingMode.onSelectionChange =
+          (anchorId, anchorOffset, focusId, focusOffset) => {
+            actualAnchorId = anchorId;
+            actualAnchorOffset = anchorOffset;
+            actualFocusId = focusId;
+            actualFocusOffset = focusOffset;
+          };
+    });
+
+    test('one node selected before single node highlight', async () => {
+      highlightNode(textNodeIds[1]!);
+
+      // select a subset of previous node
+      const expectedAnchorOffset = 0;
+      const expectedFocusOffset = 5;
+      const expectedNodeId = textNodeIds[0]!;
+      await selectNodes(
+          previousSelector, expectedAnchorOffset, texts[0]!,
+          expectedFocusOffset);
+
+      assertEquals(actualAnchorId, expectedNodeId);
+      assertEquals(actualFocusId, expectedNodeId);
+      assertEquals(actualAnchorOffset, expectedAnchorOffset);
+      assertEquals(actualFocusOffset, expectedFocusOffset);
+    });
+
+    test('multiple nodes selected before single node highlight', async () => {
+      highlightNode(textNodeIds[2]!);
+
+      // select a subset of previous nodes
+      const expectedAnchorOffset = 1;
+      const expectedFocusOffset = 7;
+      await selectNodes(
+          previousSelector, expectedAnchorOffset, texts[0]!,
+          expectedFocusOffset, texts[1]!);
+
+      assertEquals(actualAnchorId, textNodeIds[0]!);
+      assertEquals(actualFocusId, textNodeIds[1]!);
+      assertEquals(actualAnchorOffset, expectedAnchorOffset);
+      assertEquals(actualFocusOffset, expectedFocusOffset);
+    });
+
+    test('one node selected before multiple node highlight', async () => {
+      // highlight last two nodes
+      setReadingHighlight(
+          textNodeIds[2]!, 0, textNodeIds[3]!, texts[3]!.length);
+
+      // select a subset of one previous node
+      const expectedAnchorOffset = 2;
+      const expectedFocusOffset = 10;
+      const expectedNodeId = textNodeIds[1]!;
+      await selectNodes(
+          previousSelector, expectedAnchorOffset, texts[1]!,
+          expectedFocusOffset);
+
+      assertEquals(actualAnchorId, expectedNodeId);
+      assertEquals(actualFocusId, expectedNodeId);
+      assertEquals(actualAnchorOffset, expectedAnchorOffset);
+      assertEquals(actualFocusOffset, expectedFocusOffset);
+    });
+
+    test('multiple nodes selected before multiple node highlight', async () => {
+      // highlight last two nodes
+      setReadingHighlight(
+          textNodeIds[2]!, 0, textNodeIds[3]!, texts[3]!.length);
+
+      // select a subset of previous nodes
+      const expectedAnchorOffset = 1;
+      const expectedFocusOffset = 7;
+      await selectNodes(
+          previousSelector, expectedAnchorOffset, texts[0]!,
+          expectedFocusOffset, texts[1]!);
+
+      assertEquals(actualAnchorId, textNodeIds[0]!);
+      assertEquals(actualFocusId, textNodeIds[1]!);
+      assertEquals(actualAnchorOffset, expectedAnchorOffset);
+      assertEquals(actualFocusOffset, expectedFocusOffset);
+    });
+
+    test('one node selected inside single node highlight', async () => {
+      const highlightId = textNodeIds[1]!;
+      highlightNode(highlightId);
+
+      // select a subset of the highlighted node
+      const expectedAnchorOffset = 2;
+      const expectedFocusOffset = 10;
+      await selectNodes(
+          currentSelector, expectedAnchorOffset, texts[1]!,
+          expectedFocusOffset);
+
+      assertEquals(actualAnchorId, highlightId);
+      assertEquals(actualFocusId, highlightId);
+      assertEquals(actualAnchorOffset, expectedAnchorOffset);
+      assertEquals(actualFocusOffset, expectedFocusOffset);
+    });
+
+    test('one node selected inside multiple node highlight', async () => {
+      // highlight two nodes
+      setReadingHighlight(
+          textNodeIds[0]!, 0, textNodeIds[1]!, texts[1]!.length);
+
+      // select a subset of one of the highlighted nodes
+      const expectedAnchorOffset = 2;
+      const expectedFocusOffset = 10;
+      await selectNodes(
+          currentSelector, expectedAnchorOffset, texts[1]!,
+          expectedFocusOffset);
+
+      assertEquals(actualAnchorId, textNodeIds[1]!);
+      assertEquals(actualFocusId, textNodeIds[1]!);
+      assertEquals(actualAnchorOffset, expectedAnchorOffset);
+      assertEquals(actualFocusOffset, expectedFocusOffset);
+    });
+
+    test('prefix selected in long reading highlight', async () => {
+      const highlightId = textNodeIds[2]!;
+      const highlightStart = 11;
+      setReadingHighlight(
+          highlightId, highlightStart, highlightId, texts[2]!.length);
+
+      // select node up to where it's highlighted
+      const expectedAnchorOffset = 0;
+      await selectNodes(
+          previousSelector, expectedAnchorOffset,
+          texts[2]!.slice(0, highlightStart), highlightStart);
+
+      assertEquals(actualAnchorId, highlightId);
+      assertEquals(actualFocusId, highlightId);
+      assertEquals(actualAnchorOffset, expectedAnchorOffset);
+      assertEquals(actualFocusOffset, highlightStart);
+    });
+
+    test('suffix selected in long reading highlight', async () => {
+      const highlightId = textNodeIds[2]!;
+      const highlightEnd = 15;
+      setReadingHighlight(highlightId, 0, highlightId, highlightEnd);
+
+      // select node starting after the end of the highlight
+      const selectedText = texts[2]!.slice(highlightEnd);
+      const nodesToCheck = Array.from(app.shadowRoot!.querySelectorAll('span'));
+      const parentNodeWithText = nodesToCheck.find(
+          (element) => element.textContent!.includes(selectedText));
+      const textNode = parentNodeWithText!.lastChild!;
+      selection.setBaseAndExtent(textNode, 0, textNode, selectedText.length);
+      await flushTasks();
+
+      assertEquals(actualAnchorId, highlightId);
+      assertEquals(actualFocusId, highlightId);
+      assertEquals(actualAnchorOffset, highlightEnd);
+      assertEquals(actualFocusOffset, texts[2]!.length);
+    });
+
+    test('one node selected after reading highlight', async () => {
+      highlightNode(textNodeIds[1]!);
+
+      // select a subset of node after highlight
+      const expectedAnchorOffset = 2;
+      const expectedFocusOffset = 8;
+      await selectNodes(
+          afterSelector, expectedAnchorOffset, texts[2]!, expectedFocusOffset);
+
+      assertEquals(actualAnchorId, textNodeIds[2]!);
+      assertEquals(actualFocusId, textNodeIds[2]!);
+      assertEquals(actualAnchorOffset, expectedAnchorOffset);
+      assertEquals(actualFocusOffset, expectedFocusOffset);
+    });
+
+    test('multiple nodes selected after reading highlight', async () => {
+      highlightNode(textNodeIds[0]!);
+
+      // select a subset of nodes after highlight
+      const expectedAnchorOffset = 4;
+      const expectedFocusOffset = 10;
+      await selectNodes(
+          afterSelector, expectedAnchorOffset, texts[1]!, expectedFocusOffset,
+          texts[2]!);
+
+      assertEquals(actualAnchorId, textNodeIds[1]!);
+      assertEquals(actualFocusId, textNodeIds[2]!);
+      assertEquals(actualAnchorOffset, expectedAnchorOffset);
+      assertEquals(actualFocusOffset, expectedFocusOffset);
+    });
+
+    test(
+        'selection across previous, current, and after highlight', async () => {
+          // highlight middle of node 7
+          const highlightStart = 15;
+          const highlightEnd = 25;
+          setReadingHighlight(7, highlightStart, 7, highlightEnd);
+
+          // select all text
+          const expectedAnchorOffset = 0;
+          const expectedFocusOffset = texts[3]!.length;
+          const anchorNode = getTextNode(previousSelector, texts[0]!);
+          const focusText = texts[3]!;
+          const focusNode = getTextNode('p', focusText);
+          selection.setBaseAndExtent(
+              anchorNode, 0, focusNode, focusText.length);
+          await flushTasks();
+
+          assertEquals(actualAnchorId, textNodeIds[0]!);
+          assertEquals(actualFocusId, textNodeIds[3]!);
+          assertEquals(actualAnchorOffset, expectedAnchorOffset);
+          assertEquals(actualFocusOffset, expectedFocusOffset);
+        });
+  });
+
   suite('app selection is correct when selecting from the main panel: ', () => {
+    setup(() => {
+      document.onselectionchange = () => {};
+    });
+
     test('one node selected before single node highlight', () => {
       highlightNode(5);
 
@@ -252,8 +511,8 @@
 
       // The highlight should have split node 7 into two nodes - one with the
       // text that's highlighted and another with the remaining text. Since we
-      // selected the rest of node 7, the selection corresponds to the entiretly
-      // if the new second node.
+      // selected the rest of node 7, the selection corresponds to the
+      // entirety of the new second node.
       const selection = app.getSelection();
       const expectedSelectedText = texts[2]!.slice(highlightEnd);
       assertEquals(selection.anchorNode.textContent, expectedSelectedText);
@@ -296,8 +555,8 @@
 
     test('selection across previous, current, and after highlight', () => {
       // highlight middle of node 7
-      const highlightStart = texts[2]!.indexOf(',');
-      const highlightEnd = texts[2]!.lastIndexOf(',');
+      const highlightStart = 15;
+      const highlightEnd = 25;
       setReadingHighlight(7, highlightStart, 7, highlightEnd);
 
       // select all text
diff --git a/chrome/test/fuzzing/kombucha_in_process_fuzzer.cc b/chrome/test/fuzzing/kombucha_in_process_fuzzer.cc
index f2a5627..6d6ae712 100644
--- a/chrome/test/fuzzing/kombucha_in_process_fuzzer.cc
+++ b/chrome/test/fuzzing/kombucha_in_process_fuzzer.cc
@@ -84,8 +84,8 @@
 #endif
 
 void KombuchaInProcessFuzzer::SetUp() {
-  scoped_feature_list_.InitWithFeatures(
-      {features::kTabGroupsSave, features::kExtensionsMenuInAppMenu}, {});
+  scoped_feature_list_.InitWithFeatures({features::kExtensionsMenuInAppMenu},
+                                        {});
 
   // Mouse movements require enabling ui_controls manually for tests
   // that live outside the ui_interaction_test directory.
diff --git a/chrome/test/interaction/interactive_browser_test_browsertest.cc b/chrome/test/interaction/interactive_browser_test_browsertest.cc
index 2e0ec44..8b8eb5c 100644
--- a/chrome/test/interaction/interactive_browser_test_browsertest.cc
+++ b/chrome/test/interaction/interactive_browser_test_browsertest.cc
@@ -11,11 +11,17 @@
 #include "base/test/bind.h"
 #include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/test/base/test_switches.h"
+#include "components/constrained_window/constrained_window_views.h"
 #include "content/public/test/browser_test.h"
 #include "ui/base/interaction/element_identifier.h"
 #include "ui/base/interaction/element_tracker.h"
 #include "ui/base/interaction/expect_call_in_scope.h"
 #include "ui/base/interaction/interaction_sequence.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/views/view_class_properties.h"
+#include "ui/views/window/dialog_delegate.h"
 #include "url/gurl.h"
 
 namespace {
@@ -645,3 +651,124 @@
       InstrumentTab(kWebContentsId),
       NavigateWebContents(kWebContentsId, GURL("chrome://history")));
 }
+
+namespace {
+
+class TestDialog : public views::DialogDelegateView {
+ public:
+  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kElementId);
+
+  TestDialog() { SetProperty(views::kElementIdentifierKey, kElementId); }
+  ~TestDialog() override = default;
+
+  gfx::Size CalculatePreferredSize() const override {
+    return gfx::Size(200, 200);
+  }
+
+  static views::Widget* Show(Browser* parent, ui::ModalType modal_type) {
+    auto dialog = std::make_unique<TestDialog>();
+    dialog->SetModalType(modal_type);
+    views::Widget* widget = nullptr;
+    switch (modal_type) {
+      case ui::MODAL_TYPE_WINDOW:
+        widget = constrained_window::CreateBrowserModalDialogViews(
+            std::move(dialog), parent->window()->GetNativeWindow());
+        break;
+      case ui::MODAL_TYPE_CHILD:
+        widget = constrained_window::CreateWebModalDialogViews(
+            dialog.release(),
+            parent->tab_strip_model()->GetActiveWebContents());
+        break;
+      case ui::MODAL_TYPE_SYSTEM:
+      case ui::MODAL_TYPE_NONE:
+        widget = views::DialogDelegate::CreateDialogWidget(
+            std::move(dialog), nullptr,
+            BrowserView::GetBrowserViewForBrowser(parent)
+                ->GetWidget()
+                ->GetNativeView());
+        break;
+    }
+    widget->Show();
+    return widget;
+  }
+};
+
+// Scoped object that closes a widget it does not own.
+class SafeWidgetRef {
+ public:
+  SafeWidgetRef() = default;
+  SafeWidgetRef(const SafeWidgetRef&) = delete;
+  SafeWidgetRef& operator=(views::Widget* widget) {
+    if (widget != widget_) {
+      Close();
+      widget_ = widget;
+    }
+    return *this;
+  }
+  ~SafeWidgetRef() { Close(); }
+
+  void Close() {
+    if (views::Widget* widget = widget_.get()) {
+      widget_ = nullptr;
+      if (!widget->IsClosed()) {
+        widget->CloseNow();
+      }
+    }
+  }
+
+  views::Widget* operator->() { return widget_.get(); }
+
+ private:
+  raw_ptr<views::Widget> widget_ = nullptr;
+};
+
+DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(TestDialog, kElementId);
+
+}  // namespace
+
+class InteractiveBrowserTestDialogBrowsertest
+    : public InteractiveBrowserTest,
+      public testing::WithParamInterface<ui::ModalType> {
+ public:
+  InteractiveBrowserTestDialogBrowsertest() = default;
+  ~InteractiveBrowserTestDialogBrowsertest() override = default;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    InteractiveBrowserTestDialogBrowsertest,
+    ::testing::Values(ui::MODAL_TYPE_NONE,
+                      ui::MODAL_TYPE_CHILD,
+                      ui::MODAL_TYPE_WINDOW,
+                      ui::MODAL_TYPE_SYSTEM),
+    [](const testing::TestParamInfo<ui::ModalType>& param) {
+      switch (param.param) {
+        case ui::MODAL_TYPE_NONE:
+          return "None";
+        case ui::MODAL_TYPE_CHILD:
+          return "Child";
+        case ui::MODAL_TYPE_WINDOW:
+          return "Window";
+        case ui::MODAL_TYPE_SYSTEM:
+          return "System";
+      }
+    });
+
+IN_PROC_BROWSER_TEST_P(InteractiveBrowserTestDialogBrowsertest,
+                       BrowserModalDialogContext) {
+  SafeWidgetRef widget;
+  RunTestSequence(
+      Do([this, &widget]() {
+        widget = TestDialog::Show(browser(), GetParam());
+      }),
+      InAnyContext(WaitForShow(TestDialog::kElementId)),
+      InSameContext(Steps(
+          CheckView(
+              TestDialog::kElementId,
+              [](views::View* view) { return view->GetWidget()->parent(); },
+              BrowserView::GetBrowserViewForBrowser(browser())->GetWidget()),
+          CheckElement(
+              TestDialog::kElementId,
+              [](ui::TrackedElement* el) { return el->context(); },
+              browser()->window()->GetElementContext()))));
+}
diff --git a/chrome/test/python_tests b/chrome/test/python_tests
deleted file mode 160000
index 644bd77..0000000
--- a/chrome/test/python_tests
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 644bd7703b85f148564cc4038aada81f3a616d8a
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index de17395..f02137e 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -658,9 +658,11 @@
     CloseInstallCompleteDialog(GetLocalizedString(string_resource_id_to_find));
   }
 
+  scoped_refptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(scope);
+  ASSERT_TRUE(global_prefs);
   const base::Version pv =
-      base::MakeRefCounted<PersistedData>(
-          scope, CreateGlobalPrefs(scope)->GetPrefService(), nullptr)
+      base::MakeRefCounted<PersistedData>(scope, global_prefs->GetPrefService(),
+                                          nullptr)
           ->GetProductVersion(base::WideToASCII(kTestAppID));
 
   base::win::RegKey key;
diff --git a/chrome/updater/util/win_util.cc b/chrome/updater/util/win_util.cc
index 42fe0578..fae7461 100644
--- a/chrome/updater/util/win_util.cc
+++ b/chrome/updater/util/win_util.cc
@@ -1436,4 +1436,64 @@
   return GetImpersonationToken(GetExplorerPid());
 }
 
+bool IsAuditMode() {
+  base::win::RegKey setup_state_key;
+  std::wstring state;
+  return setup_state_key.Open(HKEY_LOCAL_MACHINE, kSetupStateKey,
+                              KEY_QUERY_VALUE) == ERROR_SUCCESS &&
+         setup_state_key.ReadValue(kImageStateValueName, &state) ==
+             ERROR_SUCCESS &&
+         (base::EqualsCaseInsensitiveASCII(state, kImageStateUnuseableValue) ||
+          base::EqualsCaseInsensitiveASCII(state,
+                                           kImageStateGeneralAuditValue) ||
+          base::EqualsCaseInsensitiveASCII(state,
+                                           kImageStateSpecialAuditValue));
+}
+
+bool SetOemInstallState() {
+  if (!::IsUserAnAdmin() || !IsAuditMode()) {
+    return false;
+  }
+
+  const base::Time now = base::Time::Now();
+  VLOG(1) << "OEM install time set: " << now;
+  return base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY,
+                           Wow6432(KEY_SET_VALUE))
+             .WriteValue(kRegValueOemInstallTimeMin,
+                         now.ToDeltaSinceWindowsEpoch().InMinutes()) ==
+         ERROR_SUCCESS;
+}
+
+bool ResetOemInstallState() {
+  VLOG(1) << "OEM install reset at time: " << base::Time::Now();
+  return base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY,
+                           Wow6432(KEY_SET_VALUE))
+             .DeleteValue(kRegValueOemInstallTimeMin) == ERROR_SUCCESS;
+}
+
+bool IsOemInstalling() {
+  DWORD oem_install_time_minutes = 0;
+  if (base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY,
+                        Wow6432(KEY_QUERY_VALUE))
+          .ReadValueDW(kRegValueOemInstallTimeMin, &oem_install_time_minutes) !=
+      ERROR_SUCCESS) {
+    VLOG(2) << "OemInstallTime not found";
+    return false;
+  }
+  const base::Time now = base::Time::Now();
+  const base::Time oem_install_time = base::Time::FromDeltaSinceWindowsEpoch(
+      base::Minutes(oem_install_time_minutes));
+  if (now < oem_install_time) {
+    LOG(ERROR) << "now < oem_install_time, now: " << now
+               << ", oem_install_time: " << oem_install_time;
+    return true;
+  }
+  const base::TimeDelta time_in_oem_mode = now - oem_install_time;
+  const bool is_oem_installing = time_in_oem_mode < kMinOemModeTime;
+  VLOG(1) << "now: " << now << ", OEM install time: " << oem_install_time
+          << ", time_in_oem_mode: " << time_in_oem_mode
+          << ", is_oem_installing: " << is_oem_installing;
+  return is_oem_installing;
+}
+
 }  // namespace updater
diff --git a/chrome/updater/util/win_util.h b/chrome/updater/util/win_util.h
index 4e65f84..b6cea0e 100644
--- a/chrome/updater/util/win_util.h
+++ b/chrome/updater/util/win_util.h
@@ -434,6 +434,20 @@
 // exists.
 HResultOr<ScopedKernelHANDLE> GetLoggedOnUserToken();
 
+// Returns true if running in Windows Audit mode, as documented at
+// http://technet.microsoft.com/en-us/library/cc721913.aspx.
+bool IsAuditMode();
+
+// Writes the OEM install beginning timestamp in the registry.
+bool SetOemInstallState();
+
+// Removes the OEM install beginning timestamp from the registry.
+bool ResetOemInstallState();
+
+// Returns `true` if the OEM install time is present and it has been less than
+// `kMinOemModeTime` since the OEM install.
+bool IsOemInstalling();
+
 }  // namespace updater
 
 #endif  // CHROME_UPDATER_UTIL_WIN_UTIL_H_
diff --git a/chrome/updater/util/win_util_unittest.cc b/chrome/updater/util/win_util_unittest.cc
index 5851686f..f53fd14b 100644
--- a/chrome/updater/util/win_util_unittest.cc
+++ b/chrome/updater/util/win_util_unittest.cc
@@ -591,4 +591,61 @@
   ASSERT_FALSE(::IsUserAnAdmin());
 }
 
+TEST(WinUtil, IsAuditMode) {
+  if (!::IsUserAnAdmin()) {
+    GTEST_SKIP();
+  }
+  ASSERT_FALSE(IsAuditMode());
+  ASSERT_EQ(base::win::RegKey(HKEY_LOCAL_MACHINE, kSetupStateKey, KEY_SET_VALUE)
+                .WriteValue(L"ImageState", L"IMAGE_STATE_UNDEPLOYABLE"),
+            ERROR_SUCCESS);
+  ASSERT_TRUE(IsAuditMode());
+  ASSERT_EQ(base::win::RegKey(HKEY_LOCAL_MACHINE, kSetupStateKey, KEY_SET_VALUE)
+                .DeleteValue(L"ImageState"),
+            ERROR_SUCCESS);
+}
+
+TEST(WinUtil, OemInstallState) {
+  if (!::IsUserAnAdmin()) {
+    GTEST_SKIP();
+  }
+  ASSERT_EQ(base::win::RegKey(HKEY_LOCAL_MACHINE, kSetupStateKey, KEY_SET_VALUE)
+                .WriteValue(L"ImageState", L"IMAGE_STATE_UNDEPLOYABLE"),
+            ERROR_SUCCESS);
+  ASSERT_TRUE(SetOemInstallState());
+  ASSERT_TRUE(IsOemInstalling());
+
+  DWORD oem_install_time_minutes = 0;
+  ASSERT_EQ(
+      base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY,
+                        Wow6432(KEY_QUERY_VALUE))
+          .ReadValueDW(kRegValueOemInstallTimeMin, &oem_install_time_minutes),
+      ERROR_SUCCESS);
+
+  // Rewind to 71 hours and 58 minutes before now.
+  ASSERT_EQ(
+      base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY, Wow6432(KEY_SET_VALUE))
+          .WriteValue(
+              kRegValueOemInstallTimeMin,
+              (base::Minutes(oem_install_time_minutes + 2) - kMinOemModeTime)
+                  .InMinutes()),
+      ERROR_SUCCESS);
+  ASSERT_TRUE(IsOemInstalling());
+
+  // Rewind to 72 hours and 2 minutes before now.
+  ASSERT_EQ(
+      base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY, Wow6432(KEY_SET_VALUE))
+          .WriteValue(
+              kRegValueOemInstallTimeMin,
+              (base::Minutes(oem_install_time_minutes - 2) - kMinOemModeTime)
+                  .InMinutes()),
+      ERROR_SUCCESS);
+  ASSERT_FALSE(IsOemInstalling());
+
+  ASSERT_TRUE(ResetOemInstallState());
+  ASSERT_EQ(base::win::RegKey(HKEY_LOCAL_MACHINE, kSetupStateKey, KEY_SET_VALUE)
+                .DeleteValue(L"ImageState"),
+            ERROR_SUCCESS);
+}
+
 }  // namespace updater
diff --git a/chrome/updater/win/win_constants.cc b/chrome/updater/win/win_constants.cc
index d36b974c..9813cf0 100644
--- a/chrome/updater/win/win_constants.cc
+++ b/chrome/updater/win/win_constants.cc
@@ -29,6 +29,16 @@
 const wchar_t kRegValueUninstallCmdLine[] = L"UninstallCmdLine";
 const wchar_t kRegValueVersion[] = L"version";
 
+const wchar_t kRegValueOemInstallTimeMin[] = L"OemInstallTime";
+const wchar_t kSetupStateKey[] =
+    L"Software\\Microsoft\\Windows\\CurrentVersion\\Setup\\State";
+const wchar_t kImageStateValueName[] = L"ImageState";
+const wchar_t kImageStateUnuseableValue[] = L"IMAGE_STATE_UNDEPLOYABLE";
+const wchar_t kImageStateGeneralAuditValue[] =
+    L"IMAGE_STATE_GENERALIZE_RESEAL_TO_AUDIT";
+const wchar_t kImageStateSpecialAuditValue[] =
+    L"IMAGE_STATE_SPECIALIZE_RESEAL_TO_AUDIT";
+
 const wchar_t kRegKeyCohort[] = L"cohort";
 const wchar_t kRegValueCohortName[] = L"name";
 const wchar_t kRegValueCohortHint[] = L"hint";
diff --git a/chrome/updater/win/win_constants.h b/chrome/updater/win/win_constants.h
index f5e6c6f2..5f08b22 100644
--- a/chrome/updater/win/win_constants.h
+++ b/chrome/updater/win/win_constants.h
@@ -56,6 +56,20 @@
 extern const wchar_t kRegValueUninstallCmdLine[];
 extern const wchar_t kRegValueVersion[];
 
+// Timestamp when an OEM install is started, stored as minutes since the Windows
+// Epoch.
+extern const wchar_t kRegValueOemInstallTimeMin[];
+
+// OEM installs are expected to be completed within 72 hours.
+inline constexpr base::TimeDelta kMinOemModeTime = base::Hours(72);
+
+// Windows Audit mode registry constants queried for OEM installs.
+extern const wchar_t kSetupStateKey[];
+extern const wchar_t kImageStateValueName[];
+extern const wchar_t kImageStateUnuseableValue[];
+extern const wchar_t kImageStateGeneralAuditValue[];
+extern const wchar_t kImageStateSpecialAuditValue[];
+
 // Cohort registry constants.
 extern const wchar_t kRegKeyCohort[];
 extern const wchar_t kRegValueCohortName[];
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 9233280..adc7b65 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-15847.0.0
\ No newline at end of file
+15848.0.0
\ No newline at end of file
diff --git a/chromeos/ash/components/audio/BUILD.gn b/chromeos/ash/components/audio/BUILD.gn
index 00da56e..9b49380e 100644
--- a/chromeos/ash/components/audio/BUILD.gn
+++ b/chromeos/ash/components/audio/BUILD.gn
@@ -73,6 +73,7 @@
   deps = [
     ":audio",
     "//ash/constants",
+    "//ash/strings",
     "//base/test:test_support",
     "//chromeos/ash/components/audio/public/mojom",
     "//chromeos/ash/components/dbus/audio",
@@ -83,6 +84,7 @@
     "//services/media_session/public/mojom",
     "//testing/gmock",
     "//testing/gtest",
+    "//ui/base",
     "//ui/message_center",
   ]
   sources = [
diff --git a/chromeos/ash/components/audio/audio_device_metrics_handler.cc b/chromeos/ash/components/audio/audio_device_metrics_handler.cc
index da6224e..eb2bfdc6 100644
--- a/chromeos/ash/components/audio/audio_device_metrics_handler.cc
+++ b/chromeos/ash/components/audio/audio_device_metrics_handler.cc
@@ -445,4 +445,9 @@
   }
 }
 
+void AudioDeviceMetricsHandler::RecordExceptionRulesMet(
+    AudioSelectionExceptionRules rule) {
+  base::UmaHistogramEnumeration(kAudioSelectionExceptionRuleMetrics, rule);
+}
+
 }  // namespace ash
diff --git a/chromeos/ash/components/audio/audio_device_metrics_handler.h b/chromeos/ash/components/audio/audio_device_metrics_handler.h
index 4d08209..822953ff 100644
--- a/chromeos/ash/components/audio/audio_device_metrics_handler.h
+++ b/chromeos/ash/components/audio/audio_device_metrics_handler.h
@@ -255,6 +255,12 @@
   static constexpr char kConsecutiveOutputDevicsAdded[] =
       "ChromeOS.AudioSelection.Output.ConsecutiveDevicesChangeTimeElapsed.Add";
 
+  // A histogram metric to record how often the four exception rules are met.
+  // Those rules are detailed in enum AudioSelectionExceptionRules and
+  // go/audio-io.
+  static constexpr char kAudioSelectionExceptionRuleMetrics[] =
+      "ChromeOS.AudioSelection.ExceptionRulesMet";
+
   // A series of audio selection events used to record the audio selection
   // performance. Note that these values are persisted to histograms so existing
   // values should remain unchanged and new values should be added to the end.
@@ -322,6 +328,25 @@
     kMaxValue = kUserOverrideSystemNotSwitchOutputNonChromeRestart,
   };
 
+  // A series of audio selection exception rules, detailed in go/audio-io.
+  // Note that these values are persisted to histograms so existing
+  // values should remain unchanged and new values should be added to the end.
+  enum class AudioSelectionExceptionRules {
+    // Hot plugging privileged devices.
+    kInputRule1HotPlugPrivilegedDevice = 0,
+    kOutputRule1HotPlugPrivilegedDevice = 1,
+    // Unplugging a non active device keeps current active device unchanged.
+    kInputRule2UnplugNonActiveDevice = 2,
+    kOutputRule2UnplugNonActiveDevice = 3,
+    // Hot plugging an unpreferred device keeps current active device unchanged.
+    kInputRule3HotPlugUnpreferredDevice = 4,
+    kOutputRule3HotPlugUnpreferredDevice = 5,
+    // Unplugging a device causes remaining unseen set of devices.
+    kInputRule4UnplugDeviceCausesUnseenSet = 6,
+    kOutputRule4UnplugDeviceCausesUnseenSet = 7,
+    kMaxValue = kOutputRule4UnplugDeviceCausesUnseenSet,
+  };
+
   // Record the histogram of system decision of switching or not switching after
   // audio device is added or removed. Only record if there are more than one
   // available devices.
@@ -364,6 +389,9 @@
   void RecordConsecutiveAudioDevicsChangeTimeElapsed(bool is_input,
                                                      bool is_device_added);
 
+  // Records when audio selection exception rules are met.
+  void RecordExceptionRulesMet(AudioSelectionExceptionRules rule);
+
   void set_is_chrome_restarts(bool is_chrome_restarts) {
     is_chrome_restarts_ = is_chrome_restarts;
   }
diff --git a/chromeos/ash/components/audio/audio_device_metrics_handler_unittest.cc b/chromeos/ash/components/audio/audio_device_metrics_handler_unittest.cc
index c9fe661..1afe8e2 100644
--- a/chromeos/ash/components/audio/audio_device_metrics_handler_unittest.cc
+++ b/chromeos/ash/components/audio/audio_device_metrics_handler_unittest.cc
@@ -334,4 +334,36 @@
   }
 }
 
+// Tests the audio selection exception rules metrics are fired.
+TEST_F(AudioDeviceMetricsHandlerTest, RecordExceptionRulesMet) {
+  for (int ruleInt = static_cast<int>(
+           AudioDeviceMetricsHandler::AudioSelectionExceptionRules::
+               kInputRule1HotPlugPrivilegedDevice);
+       ruleInt !=
+       static_cast<int>(
+           AudioDeviceMetricsHandler::AudioSelectionExceptionRules::kMaxValue);
+       ruleInt++) {
+    AudioDeviceMetricsHandler::AudioSelectionExceptionRules rule =
+        static_cast<AudioDeviceMetricsHandler::AudioSelectionExceptionRules>(
+            ruleInt);
+
+    // No histogram is recorded before firing.
+    histogram_tester().ExpectBucketCount(
+        AudioDeviceMetricsHandler::kAudioSelectionExceptionRuleMetrics,
+        /*bucket=*/rule,
+        /*count=*/0);
+
+    audio_device_metrics_handler().RecordExceptionRulesMet(rule);
+
+    // Histogram is recorded after firing.
+    histogram_tester().ExpectBucketCount(
+        AudioDeviceMetricsHandler::kAudioSelectionExceptionRuleMetrics,
+        /*bucket=*/rule,
+        /*count=*/1);
+    histogram_tester().ExpectTotalCount(
+        AudioDeviceMetricsHandler::kAudioSelectionExceptionRuleMetrics,
+        /*count=*/ruleInt + 1);
+  }
+}
+
 }  // namespace ash
diff --git a/chromeos/ash/components/audio/audio_device_selection_test_base.cc b/chromeos/ash/components/audio/audio_device_selection_test_base.cc
index 019ba32..c8ceab3d 100644
--- a/chromeos/ash/components/audio/audio_device_selection_test_base.cc
+++ b/chromeos/ash/components/audio/audio_device_selection_test_base.cc
@@ -94,13 +94,25 @@
   return active_node_observer_->GetActiveOutputNodeId();
 }
 
+AudioNode AudioDeviceSelectionTestBase::NewNodeWithName(
+    bool is_input,
+    const std::string& type,
+    const std::string& name) {
+  ++node_count_;
+  return AudioNode(
+      is_input, /*id=*/node_count_, /*has_v2_stable_device_id=*/true,
+      /*stable_device_id_v1=*/node_count_, /*stable_device_id_v2=*/node_count_,
+      /*device_name=*/name, type, name, /*active=*/false, /*plugged_time=*/0,
+      /*max_supported_channels=*/2, /*audio_effect=*/0,
+      /*number_of_volume_steps=*/0);
+}
+
 AudioNode AudioDeviceSelectionTestBase::NewNode(bool is_input,
                                                 const std::string& type) {
   ++node_count_;
   std::string name =
       base::StringPrintf("%s-%" PRIu64, type.c_str(), node_count_);
-  return AudioNode(is_input, node_count_, true, node_count_, node_count_, name,
-                   type, name, false, 0, 2, 0, 0);
+  return NewNodeWithName(is_input, type, name);
 }
 
 }  // namespace ash
diff --git a/chromeos/ash/components/audio/audio_device_selection_test_base.h b/chromeos/ash/components/audio/audio_device_selection_test_base.h
index 648543f..8d8e2fe 100644
--- a/chromeos/ash/components/audio/audio_device_selection_test_base.h
+++ b/chromeos/ash/components/audio/audio_device_selection_test_base.h
@@ -39,6 +39,10 @@
     return NewNode(false, type);
   }
 
+  AudioNode NewNodeWithName(bool is_input,
+                            const std::string& type,
+                            const std::string& name);
+
   void Plug(AudioNode node);
   void Unplug(const AudioNode& node);
   void Select(const AudioNode& node);
diff --git a/chromeos/ash/components/audio/audio_selection_notification_handler.cc b/chromeos/ash/components/audio/audio_selection_notification_handler.cc
index 4b3031a0..efa3de6 100644
--- a/chromeos/ash/components/audio/audio_selection_notification_handler.cc
+++ b/chromeos/ash/components/audio/audio_selection_notification_handler.cc
@@ -22,9 +22,18 @@
 
 namespace {
 
-constexpr char kAudioSelectionNotificationId[] = "audio_selection_notification";
-constexpr char kAudioSelectionNotifierId[] =
-    "ash.audio_selection_notification_handler";
+// Separator used to split the audio device name.
+constexpr char kAudioDeviceNameSeparator[] = ":";
+
+// Extracts the audio device source name of an audio device. e.g. the source
+// name for "Razer USB Sound Card: USB Audio:2,0: Mic" would be "Razer USB Sound
+// Card". The source name for "Airpods" would be "Airpods".
+std::string ExtractDeviceSourceName(const AudioDevice& device) {
+  std::vector<std::string> parts =
+      base::SplitString(device.display_name, kAudioDeviceNameSeparator,
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  return parts.front();
+}
 
 }  // namespace
 
@@ -33,6 +42,53 @@
 AudioSelectionNotificationHandler::~AudioSelectionNotificationHandler() =
     default;
 
+AudioSelectionNotificationHandler::NotificationTemplate::NotificationTemplate(
+    NotificationType type,
+    std::optional<std::string> input_device_name,
+    std::optional<std::string> output_device_name)
+    : type(type),
+      input_device_name(input_device_name),
+      output_device_name(output_device_name) {}
+
+AudioSelectionNotificationHandler::NotificationTemplate::
+    ~NotificationTemplate() = default;
+
+bool AudioSelectionNotificationHandler::AudioNodesBelongToSameSource(
+    const AudioDevice& input_device,
+    const AudioDevice& output_device) {
+  CHECK(input_device.is_input);
+  CHECK(!output_device.is_input);
+
+  // Handle internal audio device.
+  if ((input_device.type == AudioDeviceType::kInternalMic ||
+       input_device.type == AudioDeviceType::kFrontMic ||
+       input_device.type == AudioDeviceType::kRearMic) &&
+      output_device.type == AudioDeviceType::kInternalSpeaker) {
+    return true;
+  }
+
+  // Handle special cases where input and output device are the same source but
+  // different device types. kMic and kHeadphone are the types for 3.5mm jack
+  // headphone's input and output. Similarly, kBluetoothNbMic and kBluetooth are
+  // the types for a bluetooth device.
+  if ((input_device.type == AudioDeviceType::kMic &&
+       output_device.type == AudioDeviceType::kHeadphone) ||
+      (input_device.type == AudioDeviceType::kBluetoothNbMic &&
+       output_device.type == AudioDeviceType::kBluetooth)) {
+    return true;
+  }
+
+  // For other devices, different device types indicate different device
+  // sources.
+  if (input_device.type != output_device.type) {
+    return false;
+  }
+
+  // With same device type, checks their device source name.
+  return ExtractDeviceSourceName(input_device)
+             .compare(ExtractDeviceSourceName(output_device)) == 0;
+}
+
 void AudioSelectionNotificationHandler::ShowAudioSelectionNotification(
     const AudioDeviceList& hotplug_input_devices,
     const AudioDeviceList& hotplug_output_devices,
@@ -44,31 +100,67 @@
   AudioDeviceList devices_to_activate;
   std::u16string title_message_id;
   std::u16string body_message_id;
+  std::vector<message_center::ButtonInfo> buttons_info;
 
-  if (!hotplug_input_devices.empty()) {
-    devices_to_activate.push_back(hotplug_input_devices.front());
-    title_message_id =
-        l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_TITLE);
-    body_message_id = l10n_util::GetStringFUTF16(
-        IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_OR_OUTPUT_BODY,
-        base::UTF8ToUTF16(hotplug_input_devices.front().display_name));
-  } else {
-    devices_to_activate.push_back(hotplug_output_devices.front());
-    title_message_id =
-        l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_SWITCH_OUTPUT_TITLE);
-    body_message_id = l10n_util::GetStringFUTF16(
-        IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_OR_OUTPUT_BODY,
-        base::UTF8ToUTF16(hotplug_output_devices.front().display_name));
+  NotificationTemplate notification_template = GetNotificationTemplate(
+      hotplug_input_devices, hotplug_output_devices, active_input_device_name,
+      active_output_device_name);
+
+  // Use different notification titles and messages based on notification types.
+  switch (notification_template.type) {
+    case NotificationType::kSingleSourceWithInputOnly:
+      title_message_id =
+          l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_TITLE);
+      body_message_id = l10n_util::GetStringFUTF16(
+          IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_OR_OUTPUT_BODY,
+          base::UTF8ToUTF16(hotplug_input_devices.front().display_name));
+      devices_to_activate.push_back(hotplug_input_devices.front());
+      buttons_info.emplace_back(
+          l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_BUTTON_SWITCH));
+      break;
+    case NotificationType::kSingleSourceWithOutputOnly:
+      title_message_id = l10n_util::GetStringUTF16(
+          IDS_ASH_AUDIO_SELECTION_SWITCH_OUTPUT_TITLE);
+      body_message_id = l10n_util::GetStringFUTF16(
+          IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_OR_OUTPUT_BODY,
+          base::UTF8ToUTF16(hotplug_output_devices.front().display_name));
+      devices_to_activate.push_back(hotplug_output_devices.front());
+      buttons_info.emplace_back(
+          l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_BUTTON_SWITCH));
+      break;
+    case NotificationType::kSingleSourceWithInputAndOutput:
+      title_message_id = l10n_util::GetStringUTF16(
+          IDS_ASH_AUDIO_SELECTION_SWITCH_SOURCE_TITLE);
+      body_message_id = l10n_util::GetStringFUTF16(
+          IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_AND_OUTPUT_BODY,
+          base::UTF8ToUTF16(
+              ExtractDeviceSourceName(hotplug_output_devices.front())));
+      devices_to_activate.push_back(hotplug_input_devices.front());
+      devices_to_activate.push_back(hotplug_output_devices.front());
+      buttons_info.emplace_back(
+          l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_BUTTON_SWITCH));
+      break;
+    case NotificationType::kMultipleSources:
+      title_message_id = l10n_util::GetStringUTF16(
+          IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_TITLE);
+      // TODO(zhangwenyu): Check with UX how to handle rare case where existing
+      // devices' name is not available.
+      body_message_id = l10n_util::GetStringFUTF16(
+          IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_BODY,
+          base::UTF8ToUTF16(active_input_device_name.has_value()
+                                ? active_input_device_name.value()
+                                : ""),
+          base::UTF8ToUTF16(active_input_device_name.has_value()
+                                ? active_output_device_name.value()
+                                : ""));
+      buttons_info.emplace_back(
+          l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_BUTTON_SETTINGS));
+      break;
   }
 
-  std::vector<message_center::ButtonInfo> buttons_info;
-  buttons_info.emplace_back(
-      l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_BUTTON_SWITCH));
   message_center::RichNotificationData optional_fields;
   optional_fields.buttons = buttons_info;
 
-  // TODO(zhangwenyu): Handle other notification types.
-
   message_center::Notification notification{
       /*type=*/message_center::NOTIFICATION_TYPE_SIMPLE,
       /*id=*/kAudioSelectionNotificationId,
@@ -105,4 +197,67 @@
   // TODO(zhangwenyu): Activate the devices when switch button is clicked.
 }
 
+AudioSelectionNotificationHandler::NotificationTemplate
+AudioSelectionNotificationHandler::GetNotificationTemplate(
+    const AudioDeviceList& hotplug_input_devices,
+    const AudioDeviceList& hotplug_output_devices,
+    const std::optional<std::string>& active_input_device_name,
+    const std::optional<std::string>& active_output_device_name) {
+  // There must be either hotplug input devices, or hotplug output devices, or
+  // both. Otherwise, this function shouldn't be called.
+  CHECK(!hotplug_input_devices.empty() || !hotplug_output_devices.empty());
+
+  // Hot plugged devices that are used to determine notification type must be
+  // simple usage audio devices.
+  for (const AudioDevice& device : hotplug_input_devices) {
+    CHECK(device.is_for_simple_usage());
+  }
+  for (const AudioDevice& device : hotplug_output_devices) {
+    CHECK(device.is_for_simple_usage());
+  }
+
+  // If there are more than one input devices, or more than one output devices,
+  // they must come from multiple audio sources.
+  if (hotplug_input_devices.size() > 1 || hotplug_output_devices.size() > 1) {
+    return {
+        AudioSelectionNotificationHandler::NotificationType::kMultipleSources,
+        active_input_device_name, active_output_device_name};
+  }
+
+  CHECK(hotplug_input_devices.size() <= 1 &&
+        hotplug_output_devices.size() <= 1);
+
+  // If there is exactly one input and one output device, check if they belong
+  // to the same source.
+  if (hotplug_input_devices.size() == 1 && hotplug_output_devices.size() == 1) {
+    if (AudioSelectionNotificationHandler::AudioNodesBelongToSameSource(
+            hotplug_input_devices.front(), hotplug_output_devices.front())) {
+      return {AudioSelectionNotificationHandler::NotificationType::
+                  kSingleSourceWithInputAndOutput,
+              hotplug_input_devices.front().display_name,
+              hotplug_output_devices.front().display_name};
+    } else {
+      return {
+          AudioSelectionNotificationHandler::NotificationType::kMultipleSources,
+          active_input_device_name, active_output_device_name};
+    }
+  }
+
+  CHECK(hotplug_input_devices.size() + hotplug_output_devices.size() == 1);
+
+  // If there is exactly one input device and no output device, it's
+  // kSingleSourceWithInputOnly notification type.
+  if (hotplug_input_devices.size() == 1) {
+    return {AudioSelectionNotificationHandler::NotificationType::
+                kSingleSourceWithInputOnly,
+            hotplug_input_devices.front().display_name, std::nullopt};
+  }
+
+  // If there is exactly one output device and no input device, it's
+  // kSingleSourceWithOutputOnly notification type.
+  return {AudioSelectionNotificationHandler::NotificationType::
+              kSingleSourceWithOutputOnly,
+          std::nullopt, hotplug_output_devices.front().display_name};
+}
+
 }  // namespace ash
diff --git a/chromeos/ash/components/audio/audio_selection_notification_handler.h b/chromeos/ash/components/audio/audio_selection_notification_handler.h
index a506b5e..01b97e5d 100644
--- a/chromeos/ash/components/audio/audio_selection_notification_handler.h
+++ b/chromeos/ash/components/audio/audio_selection_notification_handler.h
@@ -27,6 +27,47 @@
       const AudioSelectionNotificationHandler&) = delete;
   ~AudioSelectionNotificationHandler();
 
+  // The audio selection notification id, used to identify the notification
+  // itself.
+  static constexpr char kAudioSelectionNotificationId[] =
+      "audio_selection_notification";
+
+  // The audio selection notifier id.
+  static constexpr char kAudioSelectionNotifierId[] =
+      "ash.audio_selection_notification_handler";
+
+  // Different types of audio selection notification.
+  // Do not reorder since it's used in histogram metrics.
+  enum class NotificationType {
+    // A single audio device source with only input audio device. e.g. a web
+    // cam.
+    kSingleSourceWithInputOnly = 0,
+    // A single audio device source with only output audio device. e.g. a HDMI
+    // display.
+    kSingleSourceWithOutputOnly = 1,
+    // A single audio device source with both input and output audio devices.
+    // e.g. a USB audio device.
+    kSingleSourceWithInputAndOutput = 2,
+    // Multiple audio device sources, e.g. a web cam and a HDMI display.
+    kMultipleSources = 3,
+    kMaxValue = kMultipleSources,
+  };
+
+  // Stores minimal info to create a notification. The device names will be
+  // displayed in notification body. If the notification type is
+  // kMultipleSources, device name refers to currently activated device name.
+  // Otherwise, device name refers to hot plugged device name.
+  struct NotificationTemplate {
+    NotificationTemplate(NotificationType type,
+                         std::optional<std::string> input_device_name,
+                         std::optional<std::string> output_device_name);
+    ~NotificationTemplate();
+
+    NotificationType type;
+    std::optional<std::string> input_device_name;
+    std::optional<std::string> output_device_name;
+  };
+
   // Creates and displays an audio selection notification to let users make the
   // switching or not switching decision.
   // TODO(b/333608911): Revisit audio selection notification after updated audio
@@ -38,11 +79,27 @@
       const std::optional<std::string>& active_output_device_name);
 
  private:
+  // Grant friend access for comprehensive testing of private/protected members.
+  friend class AudioSelectionNotificationHandlerTest;
+
   // Handles when the notification button is clicked.
   void HandleNotificationClicked(bool is_settings_button,
                                  const AudioDeviceList& devices_to_activate,
                                  std::optional<int> button_index);
 
+  // Checks if one audio input device and one audio output device belong to the
+  // same physical audio device.
+  bool AudioNodesBelongToSameSource(const AudioDevice& input_device,
+                                    const AudioDevice& output_device);
+
+  // Gets necessary information to create and display notitification, such as
+  // notitication type and device name.
+  NotificationTemplate GetNotificationTemplate(
+      const AudioDeviceList& hotplug_input_devices,
+      const AudioDeviceList& hotplug_output_devices,
+      const std::optional<std::string>& active_input_device_name,
+      const std::optional<std::string>& active_output_device_name);
+
   base::WeakPtrFactory<AudioSelectionNotificationHandler> weak_ptr_factory_{
       this};
 };
diff --git a/chromeos/ash/components/audio/audio_selection_notification_handler_unittest.cc b/chromeos/ash/components/audio/audio_selection_notification_handler_unittest.cc
index 537d8cb..6e47e122 100644
--- a/chromeos/ash/components/audio/audio_selection_notification_handler_unittest.cc
+++ b/chromeos/ash/components/audio/audio_selection_notification_handler_unittest.cc
@@ -4,8 +4,12 @@
 
 #include "chromeos/ash/components/audio/audio_selection_notification_handler.h"
 
+#include <optional>
+
+#include "ash/strings/grit/ash_strings.h"
 #include "chromeos/ash/components/audio/audio_device.h"
 #include "chromeos/ash/components/audio/audio_device_selection_test_base.h"
+#include "ui/base/l10n/l10n_util.h"
 
 namespace ash {
 
@@ -20,12 +24,40 @@
     return audio_selection_notification_handler_;
   }
 
-  // Get the count of audio selection notification.
+  bool AudioNodesBelongToSameSource(const AudioDevice& input_device,
+                                    const AudioDevice& output_device) {
+    return audio_selection_notification_handler_.AudioNodesBelongToSameSource(
+        input_device, output_device);
+  }
+
+  // Gets the count of audio selection notification.
   size_t GetNotificationCount() {
     auto* message_center = message_center::MessageCenter::Get();
     return message_center->NotificationCount();
   }
 
+  // Gets the title of audio selection notification. If not found, return
+  // std::nullopt.
+  const std::optional<std::u16string> GetNotificationTitle() {
+    auto* message_center = message_center::MessageCenter::Get();
+    message_center::Notification* notification =
+        message_center->FindNotificationById(
+            AudioSelectionNotificationHandler::kAudioSelectionNotificationId);
+    return notification ? std::make_optional(notification->title())
+                        : std::nullopt;
+  }
+
+  // Gets the message of audio selection notification. If not found, return
+  // std::nullopt.
+  const std::optional<std::u16string> GetNotificationMessage() {
+    auto* message_center = message_center::MessageCenter::Get();
+    message_center::Notification* notification =
+        message_center->FindNotificationById(
+            AudioSelectionNotificationHandler::kAudioSelectionNotificationId);
+    return notification ? std::make_optional(notification->message())
+                        : std::nullopt;
+  }
+
  private:
   AudioSelectionNotificationHandler audio_selection_notification_handler_;
 };
@@ -48,11 +80,231 @@
   // Expect new notification to replace the old one and the current notification
   // count does not change.
   hotplug_input_devices = {AudioDevice(NewInputNode("MIC"))};
-  hotplug_output_devices = {AudioDevice(NewOutputNode("SPEAKER"))};
+  hotplug_output_devices = {AudioDevice(NewOutputNode("HEADPHONE"))};
   audio_selection_notification_handler().ShowAudioSelectionNotification(
       hotplug_input_devices, hotplug_output_devices, std::nullopt,
       std::nullopt);
   EXPECT_EQ(1u, GetNotificationCount());
 }
 
+// Tests that AudioNodesBelongToSameSource can tell if one audio input device
+// and one audio output device belong to the same physical audio device.
+TEST_F(AudioSelectionNotificationHandlerTest, AudioNodesBelongToSameSource) {
+  struct {
+    const AudioDevice input_device;
+    const AudioDevice output_device;
+    bool same_source;
+  } items[] = {
+      {AudioDevice(NewInputNode("INTERNAL_MIC")),
+       AudioDevice(NewOutputNode("INTERNAL_SPEAKER")), true},
+      {AudioDevice(NewInputNode("FRONT_MIC")),
+       AudioDevice(NewOutputNode("INTERNAL_SPEAKER")), true},
+      {AudioDevice(NewInputNode("REAR_MIC")),
+       AudioDevice(NewOutputNode("INTERNAL_SPEAKER")), true},
+      {AudioDevice(NewInputNode("MIC")),
+       AudioDevice(NewOutputNode("HEADPHONE")), true},
+      {AudioDevice(NewInputNode("BLUETOOTH_NB_MIC")),
+       AudioDevice(NewOutputNode("BLUETOOTH")), true},
+      {AudioDevice(NewNodeWithName(/*is_input=*/true, "USB",
+                                   "Razer USB Sound Card: USB Audio:2,0: Mic")),
+       AudioDevice(
+           NewNodeWithName(/*is_input=*/false, "USB",
+                           "Razer USB Sound Card: USB Audio:2,0: Speaker")),
+       true},
+      {AudioDevice(NewNodeWithName(/*is_input=*/true, "BLUETOOTH", "Airpods")),
+       AudioDevice(NewNodeWithName(/*is_input=*/false, "BLUETOOTH", "Airpods")),
+       true},
+      // Audio devices with different types do not belong to the same physical
+      // device.
+      {AudioDevice(NewInputNode("INTERNAL_MIC")),
+       AudioDevice(NewOutputNode("HEADPHONE")), false},
+      // Audio devices with different types do not belong to the same physical
+      // device.
+      {AudioDevice(NewInputNode("BLUETOOTH")),
+       AudioDevice(NewOutputNode("HDMI")), false},
+      // Audio devices with different types do not belong to the same physical
+      // device.
+      {AudioDevice(NewInputNode("USB")), AudioDevice(NewOutputNode("HDMI")),
+       false},
+      // Audio devices with different types do not belong to the same physical
+      // device.
+      {AudioDevice(NewInputNode("BLUETOOTH")),
+       AudioDevice(NewOutputNode("USB")), false},
+      // Audio devices with different device source names do not belong to the
+      // same physical device.
+      {AudioDevice(
+           NewNodeWithName(/*is_input=*/true, "BLUETOOTH", "Airpods Pro")),
+       AudioDevice(NewNodeWithName(/*is_input=*/false, "BLUETOOTH", "Airpods")),
+       false},
+      // Audio devices with different device source names do not belong to the
+      // same physical device.
+      {AudioDevice(NewNodeWithName(/*is_input=*/true, "USB",
+                                   "Razer USB Sound Card: USB Audio:2,0: Mic")),
+       AudioDevice(NewNodeWithName(/*is_input=*/false, "USB",
+                                   "CS201 USB AUDIO: USB Audio:2,0: PCM")),
+       false},
+  };
+
+  for (const auto& item : items) {
+    EXPECT_EQ(item.same_source, AudioNodesBelongToSameSource(
+                                    item.input_device, item.output_device));
+  }
+}
+
+// Tests audio selection notification with input only displays correctly.
+TEST_F(AudioSelectionNotificationHandlerTest,
+       Notification_SingleSourceWithInputOnly) {
+  EXPECT_EQ(0u, GetNotificationCount());
+
+  // Plug a web cam input.
+  const std::string input_device_name =
+      "HD Pro Webcam C920: USB Audio:2,0: Mic";
+  AudioDeviceList hotplug_input_devices = {AudioDevice(NewNodeWithName(
+      /*is_input=*/true, "USB", input_device_name))};
+  AudioDeviceList hotplug_output_devices = {};
+  audio_selection_notification_handler().ShowAudioSelectionNotification(
+      hotplug_input_devices, hotplug_output_devices, std::nullopt,
+      std::nullopt);
+  EXPECT_EQ(1u, GetNotificationCount());
+  std::optional<std::u16string> title = GetNotificationTitle();
+  EXPECT_TRUE(title.has_value());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_TITLE),
+      title.value());
+  std::optional<std::u16string> message = GetNotificationMessage();
+  EXPECT_TRUE(message.has_value());
+  EXPECT_EQ(l10n_util::GetStringFUTF16(
+                IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_OR_OUTPUT_BODY,
+                base::UTF8ToUTF16(input_device_name)),
+            message.value());
+}
+
+// Tests audio selection notification with output only displays correctly.
+TEST_F(AudioSelectionNotificationHandlerTest,
+       Notification_SingleSourceWithOutputOnly) {
+  EXPECT_EQ(0u, GetNotificationCount());
+
+  // Plug HDMI display with audio output.
+  AudioDeviceList hotplug_input_devices = {};
+  const std::string output_device_name = "Sceptre Z27";
+  AudioDeviceList hotplug_output_devices = {AudioDevice(NewNodeWithName(
+      /*is_input=*/false, "HDMI", output_device_name))};
+  audio_selection_notification_handler().ShowAudioSelectionNotification(
+      hotplug_input_devices, hotplug_output_devices, std::nullopt,
+      std::nullopt);
+  EXPECT_EQ(1u, GetNotificationCount());
+  std::optional<std::u16string> title = GetNotificationTitle();
+  EXPECT_TRUE(title.has_value());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_SWITCH_OUTPUT_TITLE),
+      title.value());
+  std::optional<std::u16string> message = GetNotificationMessage();
+  EXPECT_TRUE(message.has_value());
+  EXPECT_EQ(l10n_util::GetStringFUTF16(
+                IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_OR_OUTPUT_BODY,
+                base::UTF8ToUTF16(output_device_name)),
+            message.value());
+}
+
+// Tests audio selection notification with single source and both input and
+// output displays correctly.
+TEST_F(AudioSelectionNotificationHandlerTest,
+       Notification_SingleSourceWithBothInputAndOutput) {
+  EXPECT_EQ(0u, GetNotificationCount());
+
+  // Plug a USB input and a USB output device from the same source.
+  const std::string device_source_name = "Razer USB Sound Card";
+  const std::string input_device_name =
+      device_source_name + ": USB Audio:2,0: Mic";
+  AudioDeviceList hotplug_input_devices = {AudioDevice(NewNodeWithName(
+      /*is_input=*/true, "USB", input_device_name))};
+  const std::string output_device_name =
+      device_source_name + ": USB Audio:2,0: Speaker";
+  AudioDeviceList hotplug_output_devices = {AudioDevice(
+      NewNodeWithName(/*is_input=*/false, "USB", output_device_name))};
+  audio_selection_notification_handler().ShowAudioSelectionNotification(
+      hotplug_input_devices, hotplug_output_devices, std::nullopt,
+      std::nullopt);
+  EXPECT_EQ(1u, GetNotificationCount());
+  std::optional<std::u16string> title = GetNotificationTitle();
+  EXPECT_TRUE(title.has_value());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_SWITCH_SOURCE_TITLE),
+      title.value());
+  std::optional<std::u16string> message = GetNotificationMessage();
+  EXPECT_TRUE(message.has_value());
+  EXPECT_EQ(l10n_util::GetStringFUTF16(
+                IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_AND_OUTPUT_BODY,
+                base::UTF8ToUTF16(device_source_name)),
+            message.value());
+}
+
+// Tests audio selection notification with multiple audio sources displays
+// correctly.
+TEST_F(AudioSelectionNotificationHandlerTest,
+       Notification_MultipleSources_SameAudioTypes) {
+  EXPECT_EQ(0u, GetNotificationCount());
+
+  // Plug a USB input and a USB output device from different sources.
+  const std::string input_device_name = "CS201 USB AUDIO: USB Audio:2,0: Mic";
+  AudioDeviceList hotplug_input_devices = {AudioDevice(NewNodeWithName(
+      /*is_input=*/true, "USB", input_device_name))};
+  const std::string output_device_name =
+      "Razer USB Sound Card: USB Audio:2,0: Speaker";
+  AudioDeviceList hotplug_output_devices = {AudioDevice(
+      NewNodeWithName(/*is_input=*/false, "USB", output_device_name))};
+  const std::string current_active_input = "internal_mic";
+  const std::string current_active_output = "internal_speaker";
+  audio_selection_notification_handler().ShowAudioSelectionNotification(
+      hotplug_input_devices, hotplug_output_devices, current_active_input,
+      current_active_output);
+  EXPECT_EQ(1u, GetNotificationCount());
+  std::optional<std::u16string> title = GetNotificationTitle();
+  EXPECT_TRUE(title.has_value());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_TITLE),
+      title.value());
+  std::optional<std::u16string> message = GetNotificationMessage();
+  EXPECT_TRUE(message.has_value());
+  EXPECT_EQ(
+      l10n_util::GetStringFUTF16(IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_BODY,
+                                 base::UTF8ToUTF16(current_active_input),
+                                 base::UTF8ToUTF16(current_active_output)),
+      message.value());
+}
+
+// Tests audio selection notification with multiple audio sources displays
+// correctly.
+TEST_F(AudioSelectionNotificationHandlerTest,
+       Notification_MultipleSources_DifferentAudioTypes) {
+  EXPECT_EQ(0u, GetNotificationCount());
+
+  // Plug a USB input and a USB output device from different sources.
+  const std::string input_device_name =
+      "HD Pro Webcam C920: USB Audio:2,0: Mic";
+  AudioDeviceList hotplug_input_devices = {AudioDevice(NewNodeWithName(
+      /*is_input=*/true, "USB", input_device_name))};
+  const std::string output_device_name = "Sceptre Z27";
+  AudioDeviceList hotplug_output_devices = {AudioDevice(NewNodeWithName(
+      /*is_input=*/false, "HDMI", output_device_name))};
+  const std::string current_active_input = "internal_mic";
+  const std::string current_active_output = "internal_speaker";
+  audio_selection_notification_handler().ShowAudioSelectionNotification(
+      hotplug_input_devices, hotplug_output_devices, current_active_input,
+      current_active_output);
+  EXPECT_EQ(1u, GetNotificationCount());
+  std::optional<std::u16string> title = GetNotificationTitle();
+  EXPECT_TRUE(title.has_value());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_TITLE),
+      title.value());
+  std::optional<std::u16string> message = GetNotificationMessage();
+  EXPECT_TRUE(message.has_value());
+  EXPECT_EQ(
+      l10n_util::GetStringFUTF16(IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_BODY,
+                                 base::UTF8ToUTF16(current_active_input),
+                                 base::UTF8ToUTF16(current_active_output)),
+      message.value());
+}
+
 }  // namespace ash
diff --git a/chromeos/ash/components/audio/cras_audio_handler.cc b/chromeos/ash/components/audio/cras_audio_handler.cc
index c23f3645..c32f461fc 100644
--- a/chromeos/ash/components/audio/cras_audio_handler.cc
+++ b/chromeos/ash/components/audio/cras_audio_handler.cc
@@ -1882,7 +1882,14 @@
     AudioDevice device = ConvertAudioNodeWithModifiedPriority(node);
     DeviceStatus status = CheckDeviceStatus(device);
     if (status == NEW_DEVICE) {
-      new_discovered->push_back(device);
+      // When audio selection improvement flag is off, always put the new device
+      // into the new discovered device list.
+      // When audio selection improvement flag is on, only put simple usage
+      // device into new discovered device list.
+      if (!features::IsAudioSelectionImprovementEnabled() ||
+          device.is_for_simple_usage()) {
+        new_discovered->push_back(device);
+      }
     }
     if (status == NEW_DEVICE || status == CHANGED_DEVICE) {
       new_or_changed_device = true;
@@ -2844,6 +2851,23 @@
   if (!preferred_device.has_value()) {
     // The device set was not seen. Show notification to let user make decision.
     should_show_notification_ = true;
+    return;
+  }
+
+  // If the preferred device is not the hot plug device and also not the
+  // currently activated device, do not switch but keep the currently active
+  // device activated, according to audio selection exception rule #3, detailed
+  // in go/audio-io.
+  const AudioDevice* current_active_device = GetDeviceFromId(
+      hotplug_device.is_input ? active_input_node_id_ : active_output_node_id_);
+  if (current_active_device && current_active_device->stable_device_id !=
+                                   preferred_device->stable_device_id) {
+    audio_device_metrics_handler_.RecordExceptionRulesMet(
+        hotplug_device.is_input
+            ? AudioDeviceMetricsHandler::AudioSelectionExceptionRules::
+                  kInputRule3HotPlugUnpreferredDevice
+            : AudioDeviceMetricsHandler::AudioSelectionExceptionRules::
+                  kOutputRule3HotPlugUnpreferredDevice);
   }
 }
 
diff --git a/chromeos/ash/components/audio/cras_audio_handler_unittest.cc b/chromeos/ash/components/audio/cras_audio_handler_unittest.cc
index a4675c7..baea712 100644
--- a/chromeos/ash/components/audio/cras_audio_handler_unittest.cc
+++ b/chromeos/ash/components/audio/cras_audio_handler_unittest.cc
@@ -6072,4 +6072,111 @@
   EXPECT_EQ(--previous_output_count, previous_output_devices.size());
 }
 
+// Tests audio selection exception rule #3 metrics are fired that hot plugging
+// an unpreferred device keeps current active device unchanged.
+TEST_P(
+    CrasAudioHandlerTest,
+    AudioSelectionExceptionRule3MetricsFired_AudioSelectionImprovementFlagOn) {
+  scoped_feature_list_.InitAndEnableFeature(
+      ash::features::kAudioSelectionImprovement);
+
+  // Set up initial audio devices.
+  SetupAudioNodesAndExpectActiveNodes(
+      /*initial_nodes=*/{kInternalSpeaker, kInternalMic},
+      /*expected_active_input_node=*/kInternalMic,
+      /*expected_active_output_node=*/kInternalSpeaker,
+      /*expected_has_alternative_input=*/false,
+      /*expected_has_alternative_output=*/false);
+
+  // Plug a HDMI output device. Expect active device remains unchanged as
+  // internal speaker.
+  AudioNodeList audio_nodes;
+  AudioNode internal_speaker = GenerateAudioNode(kInternalSpeaker);
+  internal_speaker.active = true;
+  audio_nodes.push_back(internal_speaker);
+
+  AudioNode internal_mic = GenerateAudioNode(kInternalMic);
+  internal_mic.active = true;
+  audio_nodes.push_back(internal_mic);
+
+  AudioNode hdmi_output = GenerateAudioNode(kHDMIOutput);
+  audio_nodes.push_back(hdmi_output);
+  system_monitor_observer_.reset_count();
+  ChangeAudioNodes(audio_nodes);
+
+  EXPECT_EQ(0, test_observer_->active_output_node_changed_count());
+  AudioDevice active_output;
+  EXPECT_TRUE(
+      cras_audio_handler_->GetPrimaryActiveOutputDevice(&active_output));
+  EXPECT_EQ(kInternalSpeaker->id, active_output.id);
+  EXPECT_EQ(kInternalSpeaker->id,
+            cras_audio_handler_->GetPrimaryActiveOutputNode());
+
+  // Plug a USB output device. Expect active device remains unchanged as
+  // internal speaker.
+  AudioNode usb_output = GenerateAudioNode(kUSBHeadphone1);
+  audio_nodes.push_back(usb_output);
+  ChangeAudioNodes(audio_nodes);
+
+  EXPECT_EQ(0, test_observer_->active_output_node_changed_count());
+  EXPECT_TRUE(
+      cras_audio_handler_->GetPrimaryActiveOutputDevice(&active_output));
+  EXPECT_EQ(kInternalSpeaker->id, active_output.id);
+  EXPECT_EQ(kInternalSpeaker->id,
+            cras_audio_handler_->GetPrimaryActiveOutputNode());
+
+  // Activate USB output device. Expect active device is the USB output device.
+  // Now USB is the preferred device among USB, internal and HDMI device.
+  AudioDevice usb_output_device(usb_output);
+  cras_audio_handler_->SwitchToDevice(usb_output_device, true,
+                                      CrasAudioHandler::ACTIVATE_BY_USER);
+
+  EXPECT_EQ(1, test_observer_->active_output_node_changed_count());
+  EXPECT_TRUE(
+      cras_audio_handler_->GetPrimaryActiveOutputDevice(&active_output));
+  EXPECT_EQ(kUSBHeadphone1->id, active_output.id);
+  EXPECT_EQ(kUSBHeadphone1->id,
+            cras_audio_handler_->GetPrimaryActiveOutputNode());
+
+  // Unplug HDMI output device.
+  audio_nodes.clear();
+  audio_nodes.push_back(internal_speaker);
+  audio_nodes.push_back(internal_mic);
+  audio_nodes.push_back(usb_output);
+  ChangeAudioNodes(audio_nodes);
+
+  EXPECT_EQ(2, test_observer_->active_output_node_changed_count());
+
+  // Activate internal speaker. Expect active device is the internal speaker.
+  AudioDevice internal_speaker_device(internal_speaker);
+  cras_audio_handler_->SwitchToDevice(internal_speaker_device, true,
+                                      CrasAudioHandler::ACTIVATE_BY_USER);
+
+  EXPECT_EQ(3, test_observer_->active_output_node_changed_count());
+  EXPECT_TRUE(
+      cras_audio_handler_->GetPrimaryActiveOutputDevice(&active_output));
+  EXPECT_EQ(kInternalSpeaker->id, active_output.id);
+  EXPECT_EQ(kInternalSpeaker->id,
+            cras_audio_handler_->GetPrimaryActiveOutputNode());
+
+  // Plug HDMI output device. Expect active device remains unchanged even though
+  // USB is the preferred device among USB,internal and HDMI device. Expect
+  // exception rule #3 metric is fired.
+  audio_nodes.push_back(hdmi_output);
+  ChangeAudioNodes(audio_nodes);
+
+  EXPECT_EQ(3, test_observer_->active_output_node_changed_count());
+  EXPECT_TRUE(
+      cras_audio_handler_->GetPrimaryActiveOutputDevice(&active_output));
+  EXPECT_EQ(kInternalSpeaker->id, active_output.id);
+  EXPECT_EQ(kInternalSpeaker->id,
+            cras_audio_handler_->GetPrimaryActiveOutputNode());
+
+  histogram_tester_.ExpectBucketCount(
+      AudioDeviceMetricsHandler::kAudioSelectionExceptionRuleMetrics,
+      AudioDeviceMetricsHandler::AudioSelectionExceptionRules::
+          kOutputRule3HotPlugUnpreferredDevice,
+      /*expected_count=*/1);
+}
+
 }  // namespace ash
diff --git a/chromeos/ash/components/data_migration/BUILD.gn b/chromeos/ash/components/data_migration/BUILD.gn
index 0c4d3b2d..45d2ed97 100644
--- a/chromeos/ash/components/data_migration/BUILD.gn
+++ b/chromeos/ash/components/data_migration/BUILD.gn
@@ -22,7 +22,7 @@
     ":device",
     "//base",
     "//chrome/browser/nearby_sharing/common",
-    "//chrome/browser/nearby_sharing/public/cpp",
+    "//chromeos/ash/components/nearby/common/connections_manager:connections_manager",
     "//components/keyed_service/core",
     "//third_party/nearby:platform_base_util",
   ]
@@ -51,7 +51,7 @@
 
   deps = [
     "//base",
-    "//chrome/browser/nearby_sharing/public/cpp",
+    "//chromeos/ash/components/nearby/common/connections_manager:connections_manager",
     "//chromeos/ash/services/nearby/public/mojom",
   ]
 }
@@ -67,7 +67,8 @@
     ":file_receiver",
     ":pending_file_transfer_queue",
     "//base",
-    "//chrome/browser/nearby_sharing/public/cpp",
+    "//chrome/browser/nearby_sharing/public/cpp:cpp",
+    "//chromeos/ash/components/nearby/common/connections_manager:connections_manager",
   ]
 }
 
@@ -89,7 +90,8 @@
   deps = [
     ":pending_file_transfer_queue",
     "//base",
-    "//chrome/browser/nearby_sharing/public/cpp",
+    "//chrome/browser/nearby_sharing/public/cpp:cpp",
+    "//chromeos/ash/components/nearby/common/connections_manager:connections_manager",
   ]
 }
 
@@ -109,7 +111,7 @@
     ":constants",
     "//base",
     "//base/test:test_support",
-    "//chrome/browser/nearby_sharing/public/cpp",
+    "//chromeos/ash/components/nearby/common/connections_manager",
     "//chromeos/ash/services/nearby/public/cpp",
     "//chromeos/ash/services/nearby/public/mojom",
     "//mojo",
@@ -140,7 +142,8 @@
     "//base",
     "//base/test:test_support",
     "//chrome/browser",
-    "//chrome/browser/nearby_sharing/public/cpp",
+    "//chrome/browser/nearby_sharing/public/cpp:cpp",
+    "//chromeos/ash/components/nearby/common/connections_manager",
     "//chromeos/ash/services/nearby/public/mojom",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/chromeos/ash/components/data_migration/DEPS b/chromeos/ash/components/data_migration/DEPS
index ce25054..9de4844f 100644
--- a/chromeos/ash/components/data_migration/DEPS
+++ b/chromeos/ash/components/data_migration/DEPS
@@ -3,6 +3,7 @@
   "+chrome/browser/nearby_sharing/public",
   "+chromeos/ash/services/nearby/public",
   "+components/keyed_service/core",
+  "+chromeos/ash/components/nearby/common/connections_manager",
 
   # data_migration generally does not depend directly on the third_party/nearby
   # library. It uses chrome/browser/nearby_sharing/public instead. However,
diff --git a/chromeos/ash/components/data_migration/data_migration.cc b/chromeos/ash/components/data_migration/data_migration.cc
index 2f7ff22..3cfe4402 100644
--- a/chromeos/ash/components/data_migration/data_migration.cc
+++ b/chromeos/ash/components/data_migration/data_migration.cc
@@ -11,7 +11,6 @@
 #include "base/functional/bind.h"
 #include "base/logging.h"
 #include "base/time/time.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
 #include "third_party/nearby/src/internal/platform/byte_array.h"
 #include "third_party/nearby/src/internal/platform/byte_utils.h"
 
@@ -72,7 +71,8 @@
       // `PowerLevel::kHighPower` matches what cros quick start uses and is
       // required by the `NearbyConnectionsManagerImpl` to use the bluetooth
       // classic medium.
-      BuildEndpointInfo(), this, PowerLevel::kHighPower,
+      BuildEndpointInfo(), this,
+      NearbyConnectionsManager::PowerLevel::kHighPower,
       // This causes `NearbyConnectionsManagerImpl` to disable all wifi-related
       // advertisement mechanisms (leaving only bluetooth classic). Note this
       // does not affect the medium for the main connection over which
diff --git a/chromeos/ash/components/data_migration/data_migration.h b/chromeos/ash/components/data_migration/data_migration.h
index 16f64b33..a3a5c5c 100644
--- a/chromeos/ash/components/data_migration/data_migration.h
+++ b/chromeos/ash/components/data_migration/data_migration.h
@@ -10,8 +10,8 @@
 
 #include "base/functional/callback.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chromeos/ash/components/data_migration/device.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 namespace data_migration {
diff --git a/chromeos/ash/components/data_migration/data_migration_unittest.cc b/chromeos/ash/components/data_migration/data_migration_unittest.cc
index ae432bfe..9aa7ec3 100644
--- a/chromeos/ash/components/data_migration/data_migration_unittest.cc
+++ b/chromeos/ash/components/data_migration/data_migration_unittest.cc
@@ -23,11 +23,11 @@
 #include "base/test/scoped_path_override.h"
 #include "base/test/task_environment.h"
 #include "chrome/browser/nearby_sharing/nearby_connections_manager_impl.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chromeos/ash/components/data_migration/constants.h"
 #include "chromeos/ash/components/data_migration/testing/connection_barrier.h"
 #include "chromeos/ash/components/data_migration/testing/fake_nearby_connections.h"
 #include "chromeos/ash/components/data_migration/testing/fake_nearby_process_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chromeos/ash/components/data_migration/device_unittest.cc b/chromeos/ash/components/data_migration/device_unittest.cc
index ad75fcb..5c3328ed 100644
--- a/chromeos/ash/components/data_migration/device_unittest.cc
+++ b/chromeos/ash/components/data_migration/device_unittest.cc
@@ -28,11 +28,11 @@
 #include "base/test/task_environment.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/nearby_sharing/nearby_connections_manager_impl.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chromeos/ash/components/data_migration/constants.h"
 #include "chromeos/ash/components/data_migration/testing/connection_barrier.h"
 #include "chromeos/ash/components/data_migration/testing/fake_nearby_connections.h"
 #include "chromeos/ash/components/data_migration/testing/fake_nearby_process_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chromeos/ash/components/data_migration/file_receiver.h b/chromeos/ash/components/data_migration/file_receiver.h
index a3c2c21f..40dfe96 100644
--- a/chromeos/ash/components/data_migration/file_receiver.h
+++ b/chromeos/ash/components/data_migration/file_receiver.h
@@ -11,7 +11,7 @@
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 
 namespace data_migration {
 
diff --git a/chromeos/ash/components/data_migration/file_receiver_unittest.cc b/chromeos/ash/components/data_migration/file_receiver_unittest.cc
index 6127f3e..291ca95 100644
--- a/chromeos/ash/components/data_migration/file_receiver_unittest.cc
+++ b/chromeos/ash/components/data_migration/file_receiver_unittest.cc
@@ -14,11 +14,11 @@
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "chrome/browser/nearby_sharing/nearby_connections_manager_impl.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chromeos/ash/components/data_migration/constants.h"
 #include "chromeos/ash/components/data_migration/testing/connection_barrier.h"
 #include "chromeos/ash/components/data_migration/testing/fake_nearby_connections.h"
 #include "chromeos/ash/components/data_migration/testing/fake_nearby_process_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chromeos/ash/components/data_migration/file_transfer_unittest.cc b/chromeos/ash/components/data_migration/file_transfer_unittest.cc
index 3d17647..085087d 100644
--- a/chromeos/ash/components/data_migration/file_transfer_unittest.cc
+++ b/chromeos/ash/components/data_migration/file_transfer_unittest.cc
@@ -20,12 +20,12 @@
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "chrome/browser/nearby_sharing/nearby_connections_manager_impl.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chromeos/ash/components/data_migration/constants.h"
 #include "chromeos/ash/components/data_migration/pending_file_transfer_queue.h"
 #include "chromeos/ash/components/data_migration/testing/connection_barrier.h"
 #include "chromeos/ash/components/data_migration/testing/fake_nearby_connections.h"
 #include "chromeos/ash/components/data_migration/testing/fake_nearby_process_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chromeos/ash/components/data_migration/rts_receiver_unittest.cc b/chromeos/ash/components/data_migration/rts_receiver_unittest.cc
index bfd8f97c..b07cef1 100644
--- a/chromeos/ash/components/data_migration/rts_receiver_unittest.cc
+++ b/chromeos/ash/components/data_migration/rts_receiver_unittest.cc
@@ -12,12 +12,12 @@
 #include "base/test/test_future.h"
 #include "chrome/browser/nearby_sharing/nearby_connections_manager_impl.h"
 #include "chrome/browser/nearby_sharing/public/cpp/nearby_connection.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
 #include "chromeos/ash/components/data_migration/constants.h"
 #include "chromeos/ash/components/data_migration/pending_file_transfer_queue.h"
 #include "chromeos/ash/components/data_migration/testing/connection_barrier.h"
 #include "chromeos/ash/components/data_migration/testing/fake_nearby_connections.h"
 #include "chromeos/ash/components/data_migration/testing/fake_nearby_process_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chromeos/ash/components/data_migration/testing/connection_barrier.cc b/chromeos/ash/components/data_migration/testing/connection_barrier.cc
index 3a5befb2..66e4a2ec 100644
--- a/chromeos/ash/components/data_migration/testing/connection_barrier.cc
+++ b/chromeos/ash/components/data_migration/testing/connection_barrier.cc
@@ -18,7 +18,8 @@
   CHECK(nearby_connections_manager_);
   nearby_connections_manager_->StartAdvertising(
       /*endpoint_info=*/std::vector<uint8_t>(32, 0), this,
-      PowerLevel::kHighPower, ::nearby_share::mojom::DataUsage::kOffline,
+      NearbyConnectionsManager::PowerLevel::kHighPower,
+      ::nearby_share::mojom::DataUsage::kOffline,
       // If `StartAdvertising()` fails, `Wait()` will fail with a timeout, so
       // there's no need to check this callback's return value.
       base::DoNothing());
diff --git a/chromeos/ash/components/data_migration/testing/connection_barrier.h b/chromeos/ash/components/data_migration/testing/connection_barrier.h
index 56dab048..a103ffd4 100644
--- a/chromeos/ash/components/data_migration/testing/connection_barrier.h
+++ b/chromeos/ash/components/data_migration/testing/connection_barrier.h
@@ -7,7 +7,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/test/test_future.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 
 class NearbyConnection;
 
diff --git a/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc b/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc
index 7a91b53a..317a8155 100644
--- a/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc
+++ b/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc
@@ -563,20 +563,100 @@
     const CreateP2PGroupParameter& create_group_argument,
     base::OnceCallback<void(base::Value::Dict result)> callback,
     ErrorCallback error_callback) {
-  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-      FROM_HERE,
-      base::BindOnce(std::move(error_callback), "Error", "Fake failure"));
-  return;
+  switch (simulate_create_p2p_group_result_) {
+    case FakeShillSimulatedResult::kSuccess: {
+      auto fake_success_result = base::Value::Dict().Set(
+          shill::kP2PResultCode, simulate_create_p2p_group_result_code_);
+      if (simulate_create_p2p_group_result_code_ !=
+          shill::kCreateP2PGroupResultSuccess) {
+        base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindOnce(std::move(callback),
+                                      std::move(fake_success_result)));
+        return;
+      }
+      const int shill_id = 0;
+      fake_success_result.Set(shill::kP2PDeviceShillID, shill_id);
+      auto group_owner_info = base::Value::List().Append(
+          base::Value::Dict()
+              .Set(shill::kP2PGroupInfoShillIDProperty, shill_id)
+              .Set(shill::kP2PGroupInfoInterfaceProperty, "p2p-wlan-0")
+              .Set(shill::kP2PGroupInfoStateProperty,
+                   shill::kP2PGroupInfoStateActive)
+              .Set(shill::kP2PGroupInfoSSIDProperty,
+                   create_group_argument.ssid ? *create_group_argument.ssid
+                                              : "DIRECT-A0")
+              .Set(shill::kP2PGroupInfoPassphraseProperty,
+                   create_group_argument.passphrase
+                       ? *create_group_argument.passphrase
+                       : "direct-passphrase")
+              .Set(shill::kP2PGroupInfoFrequencyProperty, 1000)
+              .Set(shill::kP2PGroupInfoNetworkIDProperty, 1));
+      SetManagerProperty(shill::kP2PGroupInfosProperty,
+                         base::Value(std::move(group_owner_info)));
+      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+          FROM_HERE,
+          base::BindOnce(std::move(callback), std::move(fake_success_result)));
+      return;
+    }
+    case FakeShillSimulatedResult::kFailure:
+      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+          FROM_HERE,
+          base::BindOnce(std::move(error_callback), "Error", "Fake failure"));
+      return;
+    case FakeShillSimulatedResult::kTimeout:
+      // No callbacks get executed and the caller should eventually timeout.
+      return;
+  }
 }
 
 void FakeShillManagerClient::ConnectToP2PGroup(
     const ConnectP2PGroupParameter& connect_group_argument,
     base::OnceCallback<void(base::Value::Dict result)> callback,
     ErrorCallback error_callback) {
-  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-      FROM_HERE,
-      base::BindOnce(std::move(error_callback), "Error", "Fake failure"));
-  return;
+  switch (simulate_connect_p2p_group_result_) {
+    case FakeShillSimulatedResult::kSuccess: {
+      auto fake_success_result = base::Value::Dict().Set(
+          shill::kP2PResultCode, simulate_connect_p2p_group_result_code_);
+      if (simulate_connect_p2p_group_result_code_ !=
+          shill::kConnectToP2PGroupResultSuccess) {
+        base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindOnce(std::move(callback),
+                                      std::move(fake_success_result)));
+        return;
+      }
+      const int shill_id = 0;
+      fake_success_result.Set(shill::kP2PDeviceShillID, shill_id);
+      auto group_owner_info = base::Value::List().Append(
+          base::Value::Dict()
+              .Set(shill::kP2PClientInfoShillIDProperty, shill_id)
+              .Set(shill::kP2PClientInfoInterfaceProperty, "p2p-wlan-0")
+              .Set(shill::kP2PClientInfoStateProperty,
+                   shill::kP2PClientInfoStateConnected)
+              .Set(shill::kP2PClientInfoSSIDProperty,
+                   connect_group_argument.ssid)
+              .Set(shill::kP2PClientInfoPassphraseProperty,
+                   connect_group_argument.passphrase)
+              .Set(shill::kP2PClientInfoFrequencyProperty,
+                   connect_group_argument.frequency
+                       ? static_cast<int>(*connect_group_argument.frequency)
+                       : 1000)
+              .Set(shill::kP2PClientInfoNetworkIDProperty, 1));
+      SetManagerProperty(shill::kP2PClientInfosProperty,
+                         base::Value(std::move(group_owner_info)));
+      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+          FROM_HERE,
+          base::BindOnce(std::move(callback), std::move(fake_success_result)));
+      return;
+    }
+    case FakeShillSimulatedResult::kFailure:
+      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+          FROM_HERE,
+          base::BindOnce(std::move(error_callback), "Error", "Fake failure"));
+      return;
+    case FakeShillSimulatedResult::kTimeout:
+      // No callbacks get executed and the caller should eventually timeout.
+      return;
+  }
 }
 
 void FakeShillManagerClient::DestroyP2PGroup(
@@ -909,6 +989,25 @@
   }
 }
 
+void FakeShillManagerClient::SetSimulateCreateP2PGroupResult(
+    FakeShillSimulatedResult operation_result,
+    const std::string& result_code) {
+  simulate_create_p2p_group_result_ = operation_result;
+  if (simulate_create_p2p_group_result_ == FakeShillSimulatedResult::kSuccess) {
+    simulate_create_p2p_group_result_code_ = result_code;
+  }
+}
+
+void FakeShillManagerClient::SetSimulateConnectToP2PGroupResult(
+    FakeShillSimulatedResult operation_result,
+    const std::string& result_code) {
+  simulate_connect_p2p_group_result_ = operation_result;
+  if (simulate_connect_p2p_group_result_ ==
+      FakeShillSimulatedResult::kSuccess) {
+    simulate_connect_p2p_group_result_code_ = result_code;
+  }
+}
+
 void FakeShillManagerClient::SetupDefaultEnvironment() {
   // Bail out from setup if there is no message loop. This will be the common
   // case for tests that are not testing Shill.
diff --git a/chromeos/ash/components/dbus/shill/fake_shill_manager_client.h b/chromeos/ash/components/dbus/shill/fake_shill_manager_client.h
index b24a0caf..f4a8b28 100644
--- a/chromeos/ash/components/dbus/shill/fake_shill_manager_client.h
+++ b/chromeos/ash/components/dbus/shill/fake_shill_manager_client.h
@@ -148,6 +148,12 @@
   void SetSimulateCheckTetheringReadinessResult(
       FakeShillSimulatedResult tethering_readiness_result,
       const std::string& readiness_status) override;
+  void SetSimulateCreateP2PGroupResult(
+      FakeShillSimulatedResult operation_result,
+      const std::string& result_code) override;
+  void SetSimulateConnectToP2PGroupResult(
+      FakeShillSimulatedResult operation_result,
+      const std::string& result_code) override;
   base::Value::List GetEnabledServiceList() const override;
   void ClearProfiles() override;
   void SetShouldReturnNullProperties(bool value) override;
@@ -231,6 +237,12 @@
   FakeShillSimulatedResult simulate_check_tethering_readiness_result_ =
       FakeShillSimulatedResult::kSuccess;
   std::string simulate_tethering_readiness_status_;
+  FakeShillSimulatedResult simulate_create_p2p_group_result_ =
+      FakeShillSimulatedResult::kSuccess;
+  std::string simulate_create_p2p_group_result_code_;
+  FakeShillSimulatedResult simulate_connect_p2p_group_result_ =
+      FakeShillSimulatedResult::kSuccess;
+  std::string simulate_connect_p2p_group_result_code_;
 
   bool return_null_properties_ = false;
   bool wifi_services_visible_by_default_ = true;
diff --git a/chromeos/ash/components/dbus/shill/shill_manager_client.cc b/chromeos/ash/components/dbus/shill/shill_manager_client.cc
index 3a5f9e2..e6163a3 100644
--- a/chromeos/ash/components/dbus/shill/shill_manager_client.cc
+++ b/chromeos/ash/components/dbus/shill/shill_manager_client.cc
@@ -26,7 +26,7 @@
 ShillManagerClient::CreateP2PGroupParameter::CreateP2PGroupParameter(
     const std::optional<std::string> ssid,
     const std::optional<std::string> passphrase,
-    const std::optional<int> frequency)
+    const std::optional<uint32_t> frequency)
     : ssid(ssid), passphrase(passphrase), frequency(frequency) {}
 
 ShillManagerClient::CreateP2PGroupParameter::~CreateP2PGroupParameter() =
@@ -35,7 +35,7 @@
 ShillManagerClient::ConnectP2PGroupParameter::ConnectP2PGroupParameter(
     const std::string ssid,
     const std::string passphrase,
-    const std::optional<int> frequency)
+    const std::optional<uint32_t> frequency)
     : ssid(ssid), passphrase(passphrase), frequency(frequency) {}
 
 ShillManagerClient::ConnectP2PGroupParameter::~ConnectP2PGroupParameter() =
@@ -268,7 +268,7 @@
 
     if (create_group_argument.frequency.has_value()) {
       properties.Set(shill::kP2PGroupInfoFrequencyProperty,
-                     create_group_argument.frequency.value());
+                     static_cast<int>(create_group_argument.frequency.value()));
     }
 
     ShillClientHelper::AppendServiceProperties(&writer, properties);
@@ -291,8 +291,9 @@
                    connect_group_argument.passphrase);
 
     if (connect_group_argument.frequency.has_value()) {
-      properties.Set(shill::kP2PGroupInfoFrequencyProperty,
-                     connect_group_argument.frequency.value());
+      properties.Set(
+          shill::kP2PGroupInfoFrequencyProperty,
+          static_cast<int>(connect_group_argument.frequency.value()));
     }
 
     ShillClientHelper::AppendServiceProperties(&writer, properties);
diff --git a/chromeos/ash/components/dbus/shill/shill_manager_client.h b/chromeos/ash/components/dbus/shill/shill_manager_client.h
index 631a82d9..3ef908c 100644
--- a/chromeos/ash/components/dbus/shill/shill_manager_client.h
+++ b/chromeos/ash/components/dbus/shill/shill_manager_client.h
@@ -49,7 +49,7 @@
   struct CreateP2PGroupParameter {
     CreateP2PGroupParameter(const std::optional<std::string> ssid,
                             const std::optional<std::string> passphrase,
-                            const std::optional<int> frequency);
+                            const std::optional<uint32_t> frequency);
 
     ~CreateP2PGroupParameter();
 
@@ -62,13 +62,13 @@
     std::optional<std::string> passphrase;
 
     // P2P group's frequency.
-    std::optional<int> frequency;
+    std::optional<uint32_t> frequency;
   };
 
   struct ConnectP2PGroupParameter {
     ConnectP2PGroupParameter(const std::string ssid,
                              const std::string passphrase,
-                             const std::optional<int> frequency);
+                             const std::optional<uint32_t> frequency);
     ~ConnectP2PGroupParameter();
 
     // SSID of the group to join.
@@ -78,7 +78,7 @@
     std::string passphrase;
 
     // Frequency of the group.
-    std::optional<int> frequency;
+    std::optional<uint32_t> frequency;
   };
 
   // Interface for setting up devices, services, and technologies for testing.
@@ -172,6 +172,18 @@
         FakeShillSimulatedResult check_readiness_result,
         const std::string& readiness_status) = 0;
 
+    // Makes CreateP2PGroup succeed, fail, or timeout and simulate the result
+    // code if it succeeds.
+    virtual void SetSimulateCreateP2PGroupResult(
+        FakeShillSimulatedResult operation_result,
+        const std::string& result_code) = 0;
+
+    // Makes ConnectToP2PGroup succeed, fail, or timeout and simulate the result
+    // code if it succeeds.
+    virtual void SetSimulateConnectToP2PGroupResult(
+        FakeShillSimulatedResult operation_result,
+        const std::string& result_code) = 0;
+
     // Clears profile list.
     virtual void ClearProfiles() = 0;
 
diff --git a/chromeos/ash/components/drivefs/drivefs_host.cc b/chromeos/ash/components/drivefs/drivefs_host.cc
index 4272c5e..bff884d 100644
--- a/chromeos/ash/components/drivefs/drivefs_host.cc
+++ b/chromeos/ash/components/drivefs/drivefs_host.cc
@@ -19,6 +19,7 @@
 #include "chromeos/ash/components/drivefs/drivefs_http_client.h"
 #include "chromeos/ash/components/drivefs/drivefs_search.h"
 #include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
+#include "chromeos/ash/components/drivefs/mojom/notifications.mojom.h"
 #include "chromeos/components/drivefs/mojom/drivefs_native_messaging.mojom.h"
 #include "components/account_id/account_id.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
@@ -250,6 +251,14 @@
     host_->delegate_->PersistMachineRootID(std::move(id));
   }
 
+  void OnNotificationReceived(
+      mojom::DriveFsNotificationPtr notification) override {
+    if (!ash::features::IsDriveFsMirroringEnabled()) {
+      return;
+    }
+    host_->delegate_->PersistNotification(std::move(notification));
+  }
+
   // Owns |this|.
   const raw_ptr<DriveFsHost> host_;
 
diff --git a/chromeos/ash/components/drivefs/drivefs_host.h b/chromeos/ash/components/drivefs/drivefs_host.h
index 46822a1..d845e7e 100644
--- a/chromeos/ash/components/drivefs/drivefs_host.h
+++ b/chromeos/ash/components/drivefs/drivefs_host.h
@@ -106,6 +106,8 @@
         mojom::DriveFsDelegate::ConnectToExtensionCallback callback) = 0;
     virtual const std::string GetMachineRootID() = 0;
     virtual void PersistMachineRootID(const std::string& id) = 0;
+    virtual void PersistNotification(
+        mojom::DriveFsNotificationPtr notification) = 0;
   };
 
   DriveFsHost(const base::FilePath& profile_path,
diff --git a/chromeos/ash/components/drivefs/drivefs_host_unittest.cc b/chromeos/ash/components/drivefs/drivefs_host_unittest.cc
index b226119..cf7ab85 100644
--- a/chromeos/ash/components/drivefs/drivefs_host_unittest.cc
+++ b/chromeos/ash/components/drivefs/drivefs_host_unittest.cc
@@ -185,6 +185,9 @@
 
   void PersistMachineRootID(const std::string& id) override {}
 
+  void PersistNotification(
+      mojom::DriveFsNotificationPtr notification) override {}
+
   const raw_ptr<signin::IdentityManager> identity_manager_;
   const AccountId account_id_;
   mojo::PendingRemote<mojom::DriveFsBootstrap> pending_bootstrap_;
diff --git a/chromeos/ash/components/drivefs/drivefs_session_unittest.cc b/chromeos/ash/components/drivefs/drivefs_session_unittest.cc
index c459842a..73c9d18 100644
--- a/chromeos/ash/components/drivefs/drivefs_session_unittest.cc
+++ b/chromeos/ash/components/drivefs/drivefs_session_unittest.cc
@@ -255,6 +255,8 @@
       mojom::DriveFsDelegate::GetMachineRootIDCallback callback) override {}
   void PersistMachineRootID(const std::string& id) override {}
   void OnMirrorSyncingStatusUpdate(mojom::SyncingStatusPtr status) override {}
+  void OnNotificationReceived(
+      mojom::DriveFsNotificationPtr notification) override {}
 };
 
 class DriveFsSessionTest : public ::testing::Test,
diff --git a/chromeos/ash/components/drivefs/mojom/BUILD.gn b/chromeos/ash/components/drivefs/mojom/BUILD.gn
index 3ad86fa..ef6e503 100644
--- a/chromeos/ash/components/drivefs/mojom/BUILD.gn
+++ b/chromeos/ash/components/drivefs/mojom/BUILD.gn
@@ -11,6 +11,7 @@
   sources = [
     "drivefs.mojom",
     "fake_drivefs_launcher.mojom",
+    "notifications.mojom",
   ]
 
   public_deps = [
diff --git a/chromeos/ash/components/drivefs/mojom/drivefs.mojom b/chromeos/ash/components/drivefs/mojom/drivefs.mojom
index ef4147f..1664c71 100644
--- a/chromeos/ash/components/drivefs/mojom/drivefs.mojom
+++ b/chromeos/ash/components/drivefs/mojom/drivefs.mojom
@@ -5,6 +5,8 @@
 module drivefs.mojom;
 
 import "chromeos/components/drivefs/mojom/drivefs_native_messaging.mojom";
+import "chromeos/ash/components/drivefs/mojom/notifications.mojom";
+
 import "mojo/public/mojom/base/file_path.mojom";
 import "mojo/public/mojom/base/time.mojom";
 
@@ -273,6 +275,9 @@
     string app_id,
     array<string> scopes) => (AccessTokenStatus status,
                               AccessToken? access_token);
+
+  // Invoked when a notification of interest is sent from DriveFS.
+  OnNotificationReceived(DriveFsNotification notification);
 };
 
 [Extensible]
diff --git a/chromeos/ash/components/drivefs/mojom/notifications.mojom b/chromeos/ash/components/drivefs/mojom/notifications.mojom
new file mode 100644
index 0000000..1fc19e4
--- /dev/null
+++ b/chromeos/ash/components/drivefs/mojom/notifications.mojom
@@ -0,0 +1,27 @@
+// 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 drivefs.mojom;
+
+import "mojo/public/mojom/base/file_path.mojom";
+
+// All notification types here should be aligned with the ones defined in
+// google3/apps/drive/fs/common/notifications.h.
+
+[Extensible]
+union DriveFsNotification {
+  // Signifies an unknown notification being sent. When using extensible union
+  // structs, they may be out of sync so to avoid this notifications sent that
+  // are unknown degrade to this and will be uniformly discarded.
+  [Default] bool unknown;
+  // The remaining fields are individual notifications, please refer to the
+  // comments on their individual structs for explanation.
+  MirrorDownloadDeletedNotification mirror_download_deleted;
+};
+
+// Notification shown when a mirrored item that was deleted on the cloud has
+// also been deleted by DriveFs locally.
+struct MirrorDownloadDeletedNotification {
+  string parent_title;
+};
diff --git a/chromeos/ash/components/feature_usage/OWNERS b/chromeos/ash/components/feature_usage/OWNERS
index 887fd8c..cd12bb92 100644
--- a/chromeos/ash/components/feature_usage/OWNERS
+++ b/chromeos/ash/components/feature_usage/OWNERS
@@ -1,2 +1 @@
-rsorokin@google.com
 hsuregan@chromium.org
diff --git a/chromeos/ash/components/nearby/common/connections_manager/BUILD.gn b/chromeos/ash/components/nearby/common/connections_manager/BUILD.gn
new file mode 100644
index 0000000..fddb885b
--- /dev/null
+++ b/chromeos/ash/components/nearby/common/connections_manager/BUILD.gn
@@ -0,0 +1,42 @@
+# 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("//build/config/chromeos/ui_mode.gni")
+
+assert(is_chromeos_ash, "Non-Chrome-OS builds must not depend on //ash")
+
+source_set("connections_manager") {
+  sources = [
+    "nearby_connections_manager.cc",
+    "nearby_connections_manager.h",
+  ]
+
+  deps = [
+    "//base",
+    "//chrome/browser/nearby_sharing/common",
+    "//chrome/browser/nearby_sharing/public/cpp:cpp",
+    "//chromeos/ash/components/nearby/presence:presence",
+    "//chromeos/ash/services/nearby/public/mojom",
+    "//third_party/abseil-cpp:absl",
+  ]
+
+  public_deps = [ "//third_party/nearby:presence_types" ]
+}
+
+source_set("test_support") {
+  testonly = true
+
+  sources = [
+    "fake_nearby_connections_manager.cc",
+    "fake_nearby_connections_manager.h",
+  ]
+
+  deps = [
+    ":connections_manager",
+    "//base",
+    "//chromeos/ash/services/nearby/public/mojom",
+  ]
+
+  public_deps = [ "//third_party/nearby:presence_types" ]
+}
diff --git a/chromeos/ash/components/nearby/common/connections_manager/DEPS b/chromeos/ash/components/nearby/common/connections_manager/DEPS
new file mode 100644
index 0000000..dc25b90
--- /dev/null
+++ b/chromeos/ash/components/nearby/common/connections_manager/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+chrome/browser/nearby_sharing/public/cpp/nearby_connection.h",
+]
\ No newline at end of file
diff --git a/chrome/browser/nearby_sharing/public/cpp/fake_nearby_connections_manager.cc b/chromeos/ash/components/nearby/common/connections_manager/fake_nearby_connections_manager.cc
similarity index 98%
rename from chrome/browser/nearby_sharing/public/cpp/fake_nearby_connections_manager.cc
rename to chromeos/ash/components/nearby/common/connections_manager/fake_nearby_connections_manager.cc
index 60b7894..20bc89f 100644
--- a/chrome/browser/nearby_sharing/public/cpp/fake_nearby_connections_manager.cc
+++ b/chromeos/ash/components/nearby/common/connections_manager/fake_nearby_connections_manager.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 "chrome/browser/nearby_sharing/public/cpp/fake_nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/fake_nearby_connections_manager.h"
 
 #include "base/containers/contains.h"
 #include "base/containers/map_util.h"
diff --git a/chrome/browser/nearby_sharing/public/cpp/fake_nearby_connections_manager.h b/chromeos/ash/components/nearby/common/connections_manager/fake_nearby_connections_manager.h
similarity index 94%
rename from chrome/browser/nearby_sharing/public/cpp/fake_nearby_connections_manager.h
rename to chromeos/ash/components/nearby/common/connections_manager/fake_nearby_connections_manager.h
index 1286584..ffa206aa 100644
--- a/chrome/browser/nearby_sharing/public/cpp/fake_nearby_connections_manager.h
+++ b/chromeos/ash/components/nearby/common/connections_manager/fake_nearby_connections_manager.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 CHROME_BROWSER_NEARBY_SHARING_PUBLIC_CPP_FAKE_NEARBY_CONNECTIONS_MANAGER_H_
-#define CHROME_BROWSER_NEARBY_SHARING_PUBLIC_CPP_FAKE_NEARBY_CONNECTIONS_MANAGER_H_
+#ifndef CHROMEOS_ASH_COMPONENTS_NEARBY_COMMON_CONNECTIONS_MANAGER_FAKE_NEARBY_CONNECTIONS_MANAGER_H_
+#define CHROMEOS_ASH_COMPONENTS_NEARBY_COMMON_CONNECTIONS_MANAGER_FAKE_NEARBY_CONNECTIONS_MANAGER_H_
 
 #include <memory>
 #include <optional>
@@ -15,7 +15,7 @@
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_connections.mojom.h"
 
 class NearbyConnection;
@@ -169,4 +169,4 @@
   base::WeakPtrFactory<FakeNearbyConnectionsManager> weak_ptr_factory_{this};
 };
 
-#endif  // CHROME_BROWSER_NEARBY_SHARING_PUBLIC_CPP_FAKE_NEARBY_CONNECTIONS_MANAGER_H_
+#endif  // CHROMEOS_ASH_COMPONENTS_NEARBY_COMMON_CONNECTIONS_MANAGER_FAKE_NEARBY_CONNECTIONS_MANAGER_H_
diff --git a/chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.cc b/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.cc
similarity index 95%
rename from chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.cc
rename to chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.cc
index bc62eb43..f09dfb841 100644
--- a/chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.cc
+++ b/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.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 "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h"
+#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
 
 // static
 std::string NearbyConnectionsManager::ConnectionsStatusToString(
diff --git a/chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h b/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h
similarity index 89%
rename from chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h
rename to chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h
index 8a1dff9..5584b59 100644
--- a/chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h
+++ b/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h
@@ -2,10 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_NEARBY_SHARING_PUBLIC_CPP_NEARBY_CONNECTIONS_MANAGER_H_
-#define CHROME_BROWSER_NEARBY_SHARING_PUBLIC_CPP_NEARBY_CONNECTIONS_MANAGER_H_
+#ifndef CHROMEOS_ASH_COMPONENTS_NEARBY_COMMON_CONNECTIONS_MANAGER_NEARBY_CONNECTIONS_MANAGER_H_
+#define CHROMEOS_ASH_COMPONENTS_NEARBY_COMMON_CONNECTIONS_MANAGER_NEARBY_CONNECTIONS_MANAGER_H_
 
 #include <stdint.h>
+
 #include <optional>
 #include <string>
 #include <vector>
@@ -13,14 +14,23 @@
 #include "base/files/file_path.h"
 #include "base/functional/callback.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
 #include "chrome/browser/nearby_sharing/public/cpp/nearby_connection.h"
 #include "chromeos/ash/components/nearby/presence/nearby_presence_service.h"
 #include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
+#include "chromeos/ash/services/nearby/public/mojom/nearby_share_settings.mojom.h"
 
 // A wrapper around the Nearby Connections mojo API.
 class NearbyConnectionsManager {
  public:
+  // Represents the advertising bluetooth power for Nearby Connections.
+  enum class PowerLevel {
+    kUnknown = 0,
+    kLowPower = 1,
+    kMediumPower = 2,
+    kHighPower = 3,
+    kMaxValue = kHighPower
+  };
+
   using PresenceDevice = nearby::presence::PresenceDevice;
   using Payload = nearby::connections::mojom::Payload;
   using PayloadPtr = nearby::connections::mojom::PayloadPtr;
@@ -28,6 +38,7 @@
   using ConnectionsCallback =
       base::OnceCallback<void(ConnectionsStatus status)>;
   using NearbyConnectionCallback = base::OnceCallback<void(NearbyConnection*)>;
+  using DataUsage = nearby_share::mojom::DataUsage;
 
   // A callback for handling incoming connections while advertising.
   class IncomingConnectionListener {
@@ -118,11 +129,12 @@
 
   // Starts advertising through Nearby Connections. Caller is expected to ensure
   // |listener| remains valid until StopAdvertising is called.
-  virtual void StartAdvertising(std::vector<uint8_t> endpoint_info,
-                                IncomingConnectionListener* listener,
-                                PowerLevel power_level,
-                                DataUsage data_usage,
-                                ConnectionsCallback callback) = 0;
+  virtual void StartAdvertising(
+      std::vector<uint8_t> endpoint_info,
+      IncomingConnectionListener* listener,
+      NearbyConnectionsManager::PowerLevel power_level,
+      DataUsage data_usage,
+      ConnectionsCallback callback) = 0;
 
   // Stops advertising through Nearby Connections.
   virtual void StopAdvertising(ConnectionsCallback callback) = 0;
@@ -204,4 +216,4 @@
   virtual base::WeakPtr<NearbyConnectionsManager> GetWeakPtr() = 0;
 };
 
-#endif  // CHROME_BROWSER_NEARBY_SHARING_PUBLIC_CPP_NEARBY_CONNECTIONS_MANAGER_H_
+#endif  // CHROMEOS_ASH_COMPONENTS_NEARBY_COMMON_CONNECTIONS_MANAGER_NEARBY_CONNECTIONS_MANAGER_H_
diff --git a/chromeos/ash/components/nearby/presence/BUILD.gn b/chromeos/ash/components/nearby/presence/BUILD.gn
index 5147b83..b6e28aa3 100644
--- a/chromeos/ash/components/nearby/presence/BUILD.gn
+++ b/chromeos/ash/components/nearby/presence/BUILD.gn
@@ -48,10 +48,10 @@
   sources = [ "nearby_presence_service_impl_unittest.cc" ]
 
   deps = [
+    ":presence",
     "//base",
     "//base/test:test_support",
     "//chrome/test:test_support",
-    "//chromeos/ash/components/nearby/presence:presence",
     "//chromeos/ash/components/nearby/presence/conversions:unit_tests",
     "//chromeos/ash/components/nearby/presence/credentials",
     "//chromeos/ash/components/nearby/presence/credentials:test_support",
diff --git a/chromeos/ash/components/network/hotspot_capabilities_provider.cc b/chromeos/ash/components/network/hotspot_capabilities_provider.cc
index 4468f54..01f4ce4a 100644
--- a/chromeos/ash/components/network/hotspot_capabilities_provider.cc
+++ b/chromeos/ash/components/network/hotspot_capabilities_provider.cc
@@ -305,7 +305,8 @@
 
 void HotspotCapabilitiesProvider::SetHotspotAllowStatus(
     hotspot_config::mojom::HotspotAllowStatus new_allow_status) {
-  if (hotspot_capabilities_.allow_status == new_allow_status) {
+  if (hotspot_capabilities_.allow_status == new_allow_status &&
+      new_allow_status == hotspot_config::mojom::HotspotAllowStatus::kAllowed) {
     return;
   }
 
diff --git a/chromeos/ash/components/network/metrics/hotspot_metrics_helper_unittest.cc b/chromeos/ash/components/network/metrics/hotspot_metrics_helper_unittest.cc
index 8c08bad..c0efbd26 100644
--- a/chromeos/ash/components/network/metrics/hotspot_metrics_helper_unittest.cc
+++ b/chromeos/ash/components/network/metrics/hotspot_metrics_helper_unittest.cc
@@ -163,18 +163,18 @@
   SetHotspotAllowStatus(
       hotspot_config::mojom::HotspotAllowStatus::kDisallowedNoMobileData);
   histogram_tester_.ExpectTotalCount(
-      HotspotMetricsHelper::kHotspotAllowStatusHistogram, 1);
+      HotspotMetricsHelper::kHotspotAllowStatusHistogram, 2);
   histogram_tester_.ExpectBucketCount(
       HotspotMetricsHelper::kHotspotAllowStatusHistogram,
       HotspotMetricsHelper::HotspotMetricsAllowStatus::kDisallowedNoMobileData,
-      1);
+      2);
   histogram_tester_.ExpectTotalCount(
       HotspotMetricsHelper::kHotspotAllowStatusAtLoginHistogram, 0);
 
   task_environment_.FastForwardBy(
       HotspotMetricsHelper::kLogAllowStatusAtLoginTimeout);
   histogram_tester_.ExpectTotalCount(
-      HotspotMetricsHelper::kHotspotAllowStatusHistogram, 1);
+      HotspotMetricsHelper::kHotspotAllowStatusHistogram, 2);
   histogram_tester_.ExpectTotalCount(
       HotspotMetricsHelper::kHotspotAllowStatusAtLoginHistogram, 1);
   histogram_tester_.ExpectBucketCount(
@@ -184,7 +184,7 @@
 
   SetHotspotAllowStatus(hotspot_config::mojom::HotspotAllowStatus::kAllowed);
   histogram_tester_.ExpectTotalCount(
-      HotspotMetricsHelper::kHotspotAllowStatusHistogram, 2);
+      HotspotMetricsHelper::kHotspotAllowStatusHistogram, 3);
   histogram_tester_.ExpectBucketCount(
       HotspotMetricsHelper::kHotspotAllowStatusHistogram,
       HotspotMetricsHelper::HotspotMetricsAllowStatus::kAllowed, 1);
diff --git a/chromeos/ash/components/quick_start/quick_start_metrics.h b/chromeos/ash/components/quick_start/quick_start_metrics.h
index 286def0..02e4bcc 100644
--- a/chromeos/ash/components/quick_start/quick_start_metrics.h
+++ b/chromeos/ash/components/quick_start/quick_start_metrics.h
@@ -32,15 +32,15 @@
                          // entry point 3).
     kQSSetUpWithAndroidPhone = 5,  // Beginning of Quick Start flow.
     kQSConnectingToWifi = 6,       // Transferring wifi with Quick Start.
-    kCheckingForUpdateAndDeterminingDeviceConfiguration = 7,
+    kCheckingForUpdateAndDeterminingDeviceConfiguration = 7,  // Critical Update
     kChooseChromebookSetup = 8,
     kConsumerUpdate = 9,
     kQSResumingConnectionAfterUpdate = 10,
     kQSGettingGoogleAccountInfo = 11,
     kQSComplete = 12,
-    kSetupDevicePIN = 13,          // After Quick Start flow is complete.
-    kAskForParentPermission = 14,  // Only for Unicorn accounts.
-    kReviewPrivacyAndTerms = 15,   // Only for Unicorn accounts.
+    kSetupDevicePIN = 13,         // After Quick Start flow is complete.
+    kAddChild = 14,               // Only for Unicorn accounts.
+    kReviewPrivacyAndTerms = 15,  // Only for Unicorn accounts.
     kUnifiedSetup = 16,    // After Quick Start flow is complete, connect host
                            // phone to account.
     kGaiaInfoScreen = 17,  // Quick Start entry point 3
diff --git a/chromeos/ash/components/quick_start/quick_start_requests.cc b/chromeos/ash/components/quick_start/quick_start_requests.cc
index 8a34ce6..f54b243 100644
--- a/chromeos/ash/components/quick_start/quick_start_requests.cc
+++ b/chromeos/ash/components/quick_start/quick_start_requests.cc
@@ -31,9 +31,9 @@
 // bootstrapOptions key containing object with phone actions after transfer.
 constexpr char kPostTransferActionKey[] = "PostTransferAction";
 // bootstrapOptions URI key inside the PostTransferAction object.
-constexpr char kURIKey[] = "uri";
+constexpr char kPostTransferActionURIKey[] = "uri";
 // bootstrapOptions PostTransferAction uri value.
-constexpr char kURIValue[] =
+constexpr char kPostTransferActionURIValue[] =
     "intent:#Intent;action=com.google.android.gms.quickstart.LANDING_SCREEN;"
     "package=com.google.android.gms;end";
 
@@ -123,8 +123,11 @@
   message->GetPayload()->Set(kDeviceTypeKey, kDeviceTypeChrome);
   message->GetPayload()->Set(kDeviceNameKey, GetDeviceName());
 
+  // TODO(b/332603236): Remove postTransferAction payload when new device info
+  // exchange is implemented.
   base::Value::Dict post_transfer_action;
-  post_transfer_action.Set(kURIKey, kURIValue);
+  post_transfer_action.Set(kPostTransferActionURIKey,
+                           kPostTransferActionURIValue);
 
   message->GetPayload()->Set(kPostTransferActionKey,
                              std::move(post_transfer_action));
@@ -227,6 +230,14 @@
       std::make_unique<QuickStartMessage>(
           QuickStartMessageType::kBootstrapState);
   message->GetPayload()->Set(kBootstrapStateKey, kBootstrapStateComplete);
+
+  // TODO(b/332603236): Remove postTransferAction payload when new device info
+  // exchange is implemented.
+  base::Value::Dict post_transfer_action;
+  post_transfer_action.Set(kPostTransferActionURIKey,
+                           kPostTransferActionURIValue);
+  message->GetPayload()->Set(kPostTransferActionKey,
+                             std::move(post_transfer_action));
   return message;
 }
 
diff --git a/chromeos/ash/services/hotspot_config/cros_hotspot_config_unittest.cc b/chromeos/ash/services/hotspot_config/cros_hotspot_config_unittest.cc
index ac23539..6af7b09 100644
--- a/chromeos/ash/services/hotspot_config/cros_hotspot_config_unittest.cc
+++ b/chromeos/ash/services/hotspot_config/cros_hotspot_config_unittest.cc
@@ -236,26 +236,26 @@
             mojom::HotspotAllowStatus::kDisallowedNoMobileData);
   EXPECT_EQ(hotspot_info->allowed_wifi_security_modes.size(), 2u);
   EXPECT_TRUE(hotspot_info->config);
-  EXPECT_EQ(observer()->hotspot_info_changed_count(), 1u);
+  EXPECT_EQ(observer()->hotspot_info_changed_count(), 2u);
 
   AddActiveCellularService();
   base::RunLoop().RunUntilIdle();
   hotspot_info = GetHotspotInfo();
   EXPECT_EQ(hotspot_info->allow_status, mojom::HotspotAllowStatus::kAllowed);
-  EXPECT_EQ(observer()->hotspot_info_changed_count(), 2u);
+  EXPECT_EQ(observer()->hotspot_info_changed_count(), 3u);
 
   SetHotspotStateInShill(shill::kTetheringStateActive);
   EXPECT_EQ(GetHotspotInfo()->state, mojom::HotspotState::kEnabled);
-  EXPECT_EQ(observer()->hotspot_info_changed_count(), 3u);
+  EXPECT_EQ(observer()->hotspot_info_changed_count(), 4u);
 
   SetHotspotStateInShill(shill::kTetheringStateIdle);
   EXPECT_EQ(GetHotspotInfo()->state, mojom::HotspotState::kDisabled);
-  EXPECT_EQ(observer()->hotspot_info_changed_count(), 4u);
+  EXPECT_EQ(observer()->hotspot_info_changed_count(), 5u);
 
   // Simulate user starting tethering
   SetHotspotStateInShill(shill::kTetheringStateStarting);
   EXPECT_EQ(GetHotspotInfo()->state, mojom::HotspotState::kEnabling);
-  EXPECT_EQ(observer()->hotspot_info_changed_count(), 5u);
+  EXPECT_EQ(observer()->hotspot_info_changed_count(), 6u);
 }
 
 TEST_F(CrosHotspotConfigTest, SetHotspotConfig) {
diff --git a/chromeos/ash/services/network_config/cros_network_config.cc b/chromeos/ash/services/network_config/cros_network_config.cc
index d269d8d2..fb44d85 100644
--- a/chromeos/ash/services/network_config/cros_network_config.cc
+++ b/chromeos/ash/services/network_config/cros_network_config.cc
@@ -13,6 +13,7 @@
 #include "base/containers/contains.h"
 #include "base/containers/flat_map.h"
 #include "base/i18n/time_formatting.h"
+#include "base/not_fatal_until.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
@@ -673,7 +674,7 @@
 std::string GetRequiredString(const base::Value::Dict* dict, const char* key) {
   const base::Value* v = dict->Find(key);
   if (!v) {
-    DUMP_WILL_BE_NOTREACHED_NORETURN() << "Required key missing: " << key;
+    NOTREACHED(base::NotFatalUntil::M127) << "Required key missing: " << key;
     return std::string();
   }
   if (!v->is_string()) {
@@ -3148,6 +3149,11 @@
   // If there is no key (in the case of non-managed devices), the default
   // mojom::GlobalPolicy() boolean value(s) specified explicitly in
   // cros_network_config.mojom is used instead.
+  if (features::IsApnPoliciesEnabled()) {
+    result->allow_apn_modification = GetBoolean(
+        global_policy_dict, ::onc::global_network_config::kAllowAPNModification,
+        /*value_if_key_missing_from_dict=*/result->allow_apn_modification);
+  }
   result->allow_cellular_sim_lock = GetBoolean(
       global_policy_dict, ::onc::global_network_config::kAllowCellularSimLock,
       /*value_if_key_missing_from_dict=*/result->allow_cellular_sim_lock);
diff --git a/chromeos/ash/services/network_config/cros_network_config_unittest.cc b/chromeos/ash/services/network_config/cros_network_config_unittest.cc
index a5ac489..e2bd0f3 100644
--- a/chromeos/ash/services/network_config/cros_network_config_unittest.cc
+++ b/chromeos/ash/services/network_config/cros_network_config_unittest.cc
@@ -4159,6 +4159,7 @@
   base::RunLoop().RunUntilIdle();
   mojom::GlobalPolicyPtr policy = GetGlobalPolicy();
   ASSERT_TRUE(policy);
+  EXPECT_TRUE(policy->allow_apn_modification);
   EXPECT_TRUE(policy->allow_cellular_sim_lock);
   EXPECT_FALSE(policy->allow_only_policy_cellular_networks);
   EXPECT_TRUE(policy->allow_only_policy_networks_to_autoconnect);
@@ -4179,6 +4180,7 @@
   EXPECT_EQ(0, observer()->GetPolicyAppliedCount(/*userhash=*/std::string()));
 
   base::Value::Dict global_config;
+  global_config.Set(::onc::global_network_config::kAllowAPNModification, false);
   global_config.Set(::onc::global_network_config::kAllowCellularSimLock, false);
   global_config.Set(::onc::global_network_config::kAllowCellularHotspot, false);
   global_config.Set(
@@ -4191,6 +4193,7 @@
   base::RunLoop().RunUntilIdle();
   mojom::GlobalPolicyPtr policy = GetGlobalPolicy();
   ASSERT_TRUE(policy);
+  EXPECT_TRUE(policy->allow_apn_modification);
   EXPECT_FALSE(policy->allow_cellular_sim_lock);
   EXPECT_FALSE(policy->allow_cellular_hotspot);
   EXPECT_TRUE(policy->allow_only_policy_cellular_networks);
@@ -4204,6 +4207,10 @@
   EXPECT_EQ(mojom::SuppressionType::kUnset, policy->allow_text_messages);
 
   EXPECT_EQ(1, observer()->GetPolicyAppliedCount(/*userhash=*/std::string()));
+
+  feature_list.InitAndEnableFeature(features::kApnPolicies);
+  policy = GetGlobalPolicy();
+  EXPECT_FALSE(policy->allow_apn_modification);
 }
 
 TEST_F(CrosNetworkConfigTest,
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 37d9f47..41851ee 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -3163,7 +3163,7 @@
           No internet. Connect to the internet and try again.
         </message>
         <message name="IDS_SEA_PEN_ERROR_RESOURCE_EXHAUSTED" desc="Error message displayed when the resource is exhausted.">
-          We're at capacity. Please come back in a bit.
+          At capacity right now. Come back soon.
         </message>
         <message name="IDS_SEA_PEN_ERROR_GENERIC" desc="Error message displayed when an error occurs.">
           Something went wrong. Try again.
@@ -3236,7 +3236,7 @@
           When you create with AI, the prompt is sent to Google AI servers to generate images and improve the product, subject to <ph name="BEGIN_LINK_GOOGLE_PRIVACY_POLICY">&lt;a target="_blank" href="https://policies.google.com/privacy"&gt;</ph>Google's Privacy Policy<ph name="END_LINK_GOOGLE_PRIVACY_POLICY">&lt;/a&gt;</ph>.
           <ph name="LINE_BREAK">&lt;br&gt;</ph>
           <ph name="LINE_BREAK">&lt;br&gt;</ph>
-          Generative AI is experimental, in early development, and currently has limited availability. <ph name="BEGIN_LINK_LEARN_MORE">&lt;a target="_blank" href="https://support.google.com/chromebook?p=copyeditor"&gt;</ph>Learn more<ph name="END_LINK_LEARN_MORE">&lt;/a&gt;</ph>
+          Generative AI is experimental, in early development, and currently has limited availability.
         </message>
         <message name="IDS_SEA_PEN_INTRODUCTION_DIALOG_CLOSE_BUTTON" desc="The button to close the introduction dialog.">
           Got it
@@ -3624,7 +3624,7 @@
           amethyst
         </message>
         <message name="IDS_SEA_PEN_OPTION_DREAMSCAPES_MATERIAL_LAPIS_LUZULI" desc="Option to fill in the DREAMSCAPES_MATERIAL placeholder for IDS_SEA_PEN_TEMPLATE_DREAMSCAPES.">
-          lapis luzuli
+          lapis lazuli
         </message>
         <message name="IDS_SEA_PEN_OPTION_DREAMSCAPES_MATERIAL_OBSIDIAN" desc="Option to fill in the DREAMSCAPES_MATERIAL placeholder for IDS_SEA_PEN_TEMPLATE_DREAMSCAPES.">
           obsidian
@@ -6147,7 +6147,7 @@
           Create new APN
         </message>
         <message name="IDS_SETTINGS_DISCOVER_MORE_APNS" desc="Title for the button which opens the discover more APNs dialog.">
-          Discover more APNs
+          Show known APNs
         </message>
         <message name="IDS_SETTINGS_APN_DESCRIPTION_NO_LINK" desc="Text describing APN settings menu without a link to more detailed information.">
           Manage network APN settings. APNs establish a connection between a cellular network and the internet.
@@ -6161,6 +6161,9 @@
         <message name="IDS_SETTINGS_APN_ZERO_STATE_DESCRIPTION" desc="Text explaining there is no APN information to show because the network has not been attempted to be connected with, and that the user might need to add a custom APN.">
           You are not connected yet. If your mobile carrier recommends a custom APN, enter the APN information by selecting "+ New APN"
         </message>
+        <message name="IDS_SETTINGS_APN_ZERO_STATE_DESCRIPTION_WITH_ADD_LINK" desc="Text shown with a add custom APN link when viewing the APN settings page and no APN available">
+          You are not connected yet. If your mobile carrier recommends a custom APN, <ph name="BEGIN_LINK">&lt;a href="#" &gt;</ph>enter the APN information.<ph name="END_LINK">&lt;/a&gt;</ph>
+        </message>
         <message name="IDS_SETTINGS_APN_DATABASE_APNS_ERROR_MESSAGE" desc="Error message displayed when a cellular network fails to connect to any database APNs due to APN-related reasons.">
           Can’t connect to this network using automatically detected APNs. Contact your mobile carrier for more information.
         </message>
@@ -6305,20 +6308,23 @@
         <message name="IDS_SETTINGS_APN_DIALOG_DONE" desc="Text used to close the APN dialog in view mode.">
           Done
         </message>
-        <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_TITLE" desc="Text used to cancel the APN dialog.">
-          Choose from available APNs
+        <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_TITLE" desc="Title of the choose APN dialog.">
+          Choose an APN
         </message>
-        <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_DESCRIPTION" desc="Text used to cancel the APN dialog.">
-          Invalid APNs could cause your mobile connection to be disabled. Only set APNs provided by your mobile provider or administrator.
+        <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_DESCRIPTION" desc="Description text in the choose APN dialog in OOBE and login screen.">
+          Make sure to select APNs provided by your mobile provider or administrator. Selecting an APN will disable any custom APNs. Invalid APNs may disrupt your mobile connection.
         </message>
-        <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_BUTTON_USE_APN" desc="Text used to close the APN dialog in view mode.">
-          Use this APN
+        <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_DESCRIPTION_WITH_LINK" desc="Description text in the choose APN dialog with a learn more link in settings.">
+          Make sure to select APNs provided by your mobile provider or administrator. Selecting an APN will disable any custom APNs. Invalid APNs may disrupt your mobile connection. <ph name="LINK_BEGIN">&lt;a href="$1" target="_blank"&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
         </message>
-        <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_ENABLED" desc="A11y announcement when 'Use this APN' button becomes enabled in the APN selection dialog.">
-          Use this APN is now enabled
+        <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_BUTTON_USE_APN" desc="'Confirm' button used to select an APN from the APN discovery dialog.">
+          Confirm
         </message>
-        <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_DISABLED" desc="A11y announcement when 'Use this APN' button becomes disabled in the APN selection dialog.">
-          Use this APN is now disabled
+        <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_ENABLED" desc="A11y announcement when 'Confirm' button becomes enabled in the APN selection dialog.">
+          Confirm button is now enabled
+        </message>
+        <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_DISABLED" desc="A11y announcement when 'Confirm' button becomes disabled in the APN selection dialog.">
+          Confirm button is now disabled
         </message>
         <message name="IDS_SETTINGS_APN_SELECTION_DIALOG_LIST_ITEM_SELECTED" desc="A11y text for checkmark in APN selection dialog list item">
           Selected
@@ -6359,7 +6365,6 @@
         <message name="IDS_INPUT_OVERLAY_EDIT_INSTRUCTIONS_TOUCH_POINT_FOCUS" desc="Text presented via a tooltip so the user knows how to interact when the touch point is focused.">
           Move the blue touch point to an action. Select the associated key to customize.
         </message>
-        <!-- TODO(b/274690042): Make the strings translatable. -->
         <message name="IDS_INPUT_OVERLAY_BUTTON_PLACEMENT_NUDGE_TITLE" desc="Button placement mode rich nudge title text.">
           Create control
         </message>
@@ -6393,43 +6398,43 @@
          <message name="IDS_INPUT_OVERLAY_CONTROL_NAME_LABEL_UNASSIGNED_TEMPLATE" desc="The Control name is formed by Control type and assigned keys. For example, Unassigned button or Unassigned Joystick.">
           Unassigned <ph name="CONTROL_TYPE">$1<ex>Button</ex></ph>
         </message>
-        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_BUTTON_KEYBOARD_A11Y_TPL" desc="Edit label a11y label. Spoken by screen readers when the list item button gets focus but not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_BUTTON_KEYBOARD_A11Y_TPL" desc="Edit label a11y label. Spoken by screen readers when the list item button gets focus but not visually rendered.">
           Selected key is <ph name="KEYS">$1<ex>w</ex></ph>. <ph name="ASSIGN_INSTRUCTION">$2<ex>assign_instruction</ex></ph>
         </message>
-        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_BUTTON_KEYBOARD_UNASSIGNED_A11Y_TPL" desc="A11y name for symbol '?' or empty string and spoken by screen readers when symbol ? or nothing renders in key label and gets focus but the name not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_BUTTON_KEYBOARD_UNASSIGNED_A11Y_TPL" desc="A11y name for symbol '?' or empty string and spoken by screen readers when symbol ? or nothing renders in key label and gets focus but the name not visually rendered.">
           Key is missing. <ph name="REASSIGN_INSTRUCTION">$1<ex>reassign_instruction</ex></ph>
         </message>
-        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_JOYSTICK_KEYBOARD_A11Y_TPL" desc="Edit label a11y label for joystick keyboard binding. Spoken by screen readers when the list item button gets focus but not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_JOYSTICK_KEYBOARD_A11Y_TPL" desc="Edit label a11y label for joystick keyboard binding. Spoken by screen readers when the list item button gets focus but not visually rendered.">
           Selected key is <ph name="KEYS">$1<ex>w</ex></ph> for <ph name="DIRECTION">$2<ex>up</ex></ph>. <ph name="REASSIGN_INSTRUCTION">$3<ex>reassign_instruction</ex></ph>
         </message>
-        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_JOYSTICK_KEYBOARD_UNASSIGNED_A11Y_TPL" desc="A11y name for joystick keyboard binding symbol '?' or empty string and spoken by screen readers when symbol ? or nothing renders in key label and gets focus but the name not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_JOYSTICK_KEYBOARD_UNASSIGNED_A11Y_TPL" desc="A11y name for joystick keyboard binding symbol '?' or empty string and spoken by screen readers when symbol ? or nothing renders in key label and gets focus but the name not visually rendered.">
           Key is missing for <ph name="DIRECTION">$1<ex>up</ex></ph>. <ph name="ASSIGN_INSTRUCTION">$2<ex>assign_instruction</ex></ph>
         </message>
-        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_KEYBOARD_ASSIGN_INSTRUCTION_A11Y_LABEL" desc="Edit label button a11y label as assignment instruction. Spoken by screen readers when the list item button gets focus but not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_KEYBOARD_ASSIGN_INSTRUCTION_A11Y_LABEL" desc="Edit label button a11y label as assignment instruction. Spoken by screen readers when the list item button gets focus but not visually rendered.">
           Tap on a keyboard key to assign
         </message>
-        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_KEYBOARD_REASSIGN_INSTRUCTION_A11Y_LABEL" desc="Edit label button a11y label as replacement instruction. Spoken by screen readers when the list item button gets focus but not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_EDIT_LABEL_KEYBOARD_REASSIGN_INSTRUCTION_A11Y_LABEL" desc="Edit label button a11y label as replacement instruction. Spoken by screen readers when the list item button gets focus but not visually rendered.">
           Tap on another keyboard key to replace
         </message>
-        <message name="IDS_INPUT_OVERLAY_JOYSTICK_DIRECTION_UP_A11Y_LABEL" desc="A11y name for joystick direction when the up label is focused. It's not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_JOYSTICK_DIRECTION_UP_A11Y_LABEL" desc="A11y name for joystick direction when the up label is focused. It's not visually rendered.">
           up
         </message>
-        <message name="IDS_INPUT_OVERLAY_JOYSTICK_DIRECTION_LEFT_A11Y_LABEL" desc="A11y name for joystick direction when the left label is focused. It's not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_JOYSTICK_DIRECTION_LEFT_A11Y_LABEL" desc="A11y name for joystick direction when the left label is focused. It's not visually rendered.">
           left
         </message>
-        <message name="IDS_INPUT_OVERLAY_JOYSTICK_DIRECTION_DOWN_A11Y_LABEL" desc="A11y name for joystick direction when the down label is focused. It's not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_JOYSTICK_DIRECTION_DOWN_A11Y_LABEL" desc="A11y name for joystick direction when the down label is focused. It's not visually rendered.">
           down
         </message>
-        <message name="IDS_INPUT_OVERLAY_JOYSTICK_DIRECTION_RIGHT_A11Y_LABEL" desc="A11y name for joystick direction when the right label is focused. It's not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_JOYSTICK_DIRECTION_RIGHT_A11Y_LABEL" desc="A11y name for joystick direction when the right label is focused. It's not visually rendered.">
           right
         </message>
         <message name="IDS_INPUT_OVERLAY_EDITING_DONE_BUTTON_LABEL" desc="The title of buttons in the Game Controls menu indicating to save and close either the main or editor menu.">
           Done
         </message>
-        <message name="IDS_INPUT_OVERLAY_BUTTON_OPTIONS_ACTION_EDIT_BUTTON_A11Y_LABEL" desc="Action edit button a11y label for button options menu. Spoken by screen readers when the list item button gets focus but not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_BUTTON_OPTIONS_ACTION_EDIT_BUTTON_A11Y_LABEL" desc="Action edit button a11y label for button options menu. Spoken by screen readers when the list item button gets focus but not visually rendered.">
           Tap on the button to focus on the key assignment
         </message>
-        <message name="IDS_INPUT_OVERLAY_BUTTON_PLACEMENT_A11Y_LABEL" desc="A11y label for button placement mode. Spoken by screen readers when in button placement mode but not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_BUTTON_PLACEMENT_A11Y_LABEL" desc="A11y label for button placement mode. Spoken by screen readers when in button placement mode but not visually rendered.">
           Use arrow keys to move this control to the game action you want. Use the enter key to place the control. Use the escape key to cancel.
         </message>
         <message name="IDS_INPUT_OVERLAY_EDITING_LIST_FIRST_CONTROL_LABEL" desc="The label to the left of the '+' button in the row below the title in the Game Controls editor menu without having created any buttons.">
@@ -6450,19 +6455,22 @@
         <message name="IDS_INPUT_OVERLAY_EDITING_LIST_HELP_BUTTON_NAME" desc="Helper button tooltip text and a11y label.">
           Get help
         </message>
-        <message name="IDS_INPUT_OVERLAY_EDITING_LIST_DONE_BUTTON_A11Y_LABEL" desc="Done button a11y label. Spoken by screen readers when the list item button gets focus but not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_EDITING_LIST_DONE_BUTTON_A11Y_LABEL" desc="Done button a11y label. Spoken by screen readers when the list item button gets focus but not visually rendered.">
           Done editing
         </message>
-        <message name="IDS_INPUT_OVERLAY_EDITING_LIST_ITEM_BUTTON_CONTAINER_A11Y_TPL" desc="List item button a11y label for editing list when including one single edit label. Spoken by screen readers when the list item button gets focus but not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_EDITING_LIST_ITEM_BUTTON_CONTAINER_A11Y_TPL" desc="List item button a11y label for editing list when including one single edit label. Spoken by screen readers when the list item button gets focus but not visually rendered.">
           Selected key is <ph name="KEYS">$1<ex>w</ex></ph>. Tap on the button to edit the control
         </message>
-        <message name="IDS_INPUT_OVERLAY_EDITING_LIST_ITEM_JOYSTICK_CONTAINER_A11Y_TPL" desc="List item button a11y label for editing list when including more than one edit label. Spoken by screen readers when list item button gets focus but not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_EDITING_LIST_ITEM_JOYSTICK_CONTAINER_A11Y_TPL" desc="List item button a11y label for editing list when including more than one edit label. Spoken by screen readers when list item button gets focus but not visually rendered.">
           Selected joystick keys are <ph name="KEYS">$1<ex>w, a, s, d</ex></ph>. Tap on the button to edit the control
         </message>
-        <message name="IDS_INPUT_OVERLAY_SHORTCUT_EDIT_A11Y_LABEL_TEMPLATE" desc="Edit button a11y label for delete-edit shortcut menu. Spoken by screen readers when the list item button gets focus but not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_SHORTCUT_MENU_A11Y_LABEL" desc="A11y label for delete-edit shortcut menu. Spoken by screen readers when the edit and delete menu gets focus but not visually rendered.">
+          Edit and delete menu
+        </message>
+        <message name="IDS_INPUT_OVERLAY_SHORTCUT_EDIT_A11Y_LABEL_TEMPLATE" desc="Edit button a11y label for delete-edit shortcut menu. Spoken by screen readers when the list item button gets focus but not visually rendered.">
           Edit <ph name="ACTION_NAME">$1<ex>Button w</ex></ph>
         </message>
-        <message name="IDS_INPUT_OVERLAY_SHORTCUT_DELETE_A11Y_LABEL_TEMPLATE" desc="Delete button a11y label for delete-edit shortcut menu. Spoken by screen readers when the list item button gets focus but not visually rendered." translateable="false">
+        <message name="IDS_INPUT_OVERLAY_SHORTCUT_DELETE_A11Y_LABEL_TEMPLATE" desc="Delete button a11y label for delete-edit shortcut menu. Spoken by screen readers when the list item button gets focus but not visually rendered.">
           Delete <ph name="ACTION_NAME">$1<ex>Button w</ex></ph>
         </message>
         <!-- End of Arc Input Overlay -->
diff --git a/chromeos/chromeos_strings_grd/IDS_INPUT_OVERLAY_SHORTCUT_MENU_A11Y_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_INPUT_OVERLAY_SHORTCUT_MENU_A11Y_LABEL.png.sha1
new file mode 100644
index 0000000..df68516
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_INPUT_OVERLAY_SHORTCUT_MENU_A11Y_LABEL.png.sha1
@@ -0,0 +1 @@
+1d610edaf4eadf2bd2cf5544236a41f361136822
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_ERROR_RESOURCE_EXHAUSTED.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_ERROR_RESOURCE_EXHAUSTED.png.sha1
index cc62b96..1956061 100644
--- a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_ERROR_RESOURCE_EXHAUSTED.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_ERROR_RESOURCE_EXHAUSTED.png.sha1
@@ -1 +1 @@
-a7ecfe1f4ffbc783cefe43c657caaaa949e215d8
\ No newline at end of file
+fca9e276d455302ff99ecd5b6670b1b252185078
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_INTRODUCTION_DIALOG_CONTENT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_INTRODUCTION_DIALOG_CONTENT.png.sha1
index ced551f..af8bbd1 100644
--- a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_INTRODUCTION_DIALOG_CONTENT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_INTRODUCTION_DIALOG_CONTENT.png.sha1
@@ -1 +1 @@
-06e71ef9b9685d9dcb4dccba0e86d01073bb5d87
\ No newline at end of file
+4e0652cb0213d97337f55fddec5c4a15a933a8f3
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_DREAMSCAPES_MATERIAL_LAPIS_LUZULI.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_DREAMSCAPES_MATERIAL_LAPIS_LUZULI.png.sha1
index e82cdd72..37cb7b7 100644
--- a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_DREAMSCAPES_MATERIAL_LAPIS_LUZULI.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_DREAMSCAPES_MATERIAL_LAPIS_LUZULI.png.sha1
@@ -1 +1 @@
-47e9cc98cbfb1cf99c3799a026cb5843c72f81e4
\ No newline at end of file
+29bb8bbd82af621d6572c5e66297e88e5dc1784c
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN.png.sha1
new file mode 100644
index 0000000..b03b198
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN.png.sha1
@@ -0,0 +1 @@
+c3537c756735c602be7a474ae74a24421d76a760
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_DISABLED.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_DISABLED.png.sha1
index b1c9d48..b03b198 100644
--- a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_DISABLED.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_DISABLED.png.sha1
@@ -1 +1 @@
-af58f7f2b6e88a0f54ba85111526bb6a1f48665c
\ No newline at end of file
+c3537c756735c602be7a474ae74a24421d76a760
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_ENABLED.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_ENABLED.png.sha1
index a14311a3..a8e899c 100644
--- a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_ENABLED.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_A11Y_USE_APN_ENABLED.png.sha1
@@ -1 +1 @@
-2e944303dbef7e8aa22ffd7f7d1c28171285c534
\ No newline at end of file
+d9089b8970f910d7a19a64290ad7f2fa2e694dc0
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_BUTTON_USE_APN.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_BUTTON_USE_APN.png.sha1
index a7b36d45..a8e899c 100644
--- a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_BUTTON_USE_APN.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_BUTTON_USE_APN.png.sha1
@@ -1 +1 @@
-5a76c3fac2cc43f8bcccbcd3d429d005402fa949
\ No newline at end of file
+d9089b8970f910d7a19a64290ad7f2fa2e694dc0
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_DESCRIPTION.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_DESCRIPTION.png.sha1
index a7b36d45..1ec4fc7 100644
--- a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_DESCRIPTION.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_DESCRIPTION.png.sha1
@@ -1 +1 @@
-5a76c3fac2cc43f8bcccbcd3d429d005402fa949
\ No newline at end of file
+5d2a6f7f1b53b1a63573fe4fedafffb6ca0be41f
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_DESCRIPTION_WITH_LINK.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_DESCRIPTION_WITH_LINK.png.sha1
new file mode 100644
index 0000000..41db9f1
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_DESCRIPTION_WITH_LINK.png.sha1
@@ -0,0 +1 @@
+e7d12f30ed0ec935e726d8da7ae955c024e64dee
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_TITLE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_TITLE.png.sha1
index a7b36d45..b03b198 100644
--- a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_TITLE.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_SELECTION_DIALOG_TITLE.png.sha1
@@ -1 +1 @@
-5a76c3fac2cc43f8bcccbcd3d429d005402fa949
\ No newline at end of file
+c3537c756735c602be7a474ae74a24421d76a760
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_ZERO_STATE_DESCRIPTION_WITH_ADD_LINK.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_ZERO_STATE_DESCRIPTION_WITH_ADD_LINK.png.sha1
new file mode 100644
index 0000000..cd3f3b79
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SETTINGS_APN_ZERO_STATE_DESCRIPTION_WITH_ADD_LINK.png.sha1
@@ -0,0 +1 @@
+2425b5ec9ad2eb3751f150161fb900c6d8caa3e6
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SETTINGS_DISCOVER_MORE_APNS.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SETTINGS_DISCOVER_MORE_APNS.png.sha1
index 6ba5609..e2dc94b 100644
--- a/chromeos/chromeos_strings_grd/IDS_SETTINGS_DISCOVER_MORE_APNS.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_SETTINGS_DISCOVER_MORE_APNS.png.sha1
@@ -1 +1 @@
-de8a6e9ccd6571964132ad32ac55daece51ad868
\ No newline at end of file
+1269a16d42ed1a6ce645e315f20f30fb89299909
\ No newline at end of file
diff --git a/chromeos/crosapi/mojom/app_service_types_mojom_traits.cc b/chromeos/crosapi/mojom/app_service_types_mojom_traits.cc
index 6acbb153..f3d087cc 100644
--- a/chromeos/crosapi/mojom/app_service_types_mojom_traits.cc
+++ b/chromeos/crosapi/mojom/app_service_types_mojom_traits.cc
@@ -316,7 +316,7 @@
   // version skew caused the type to be dropped. Reset the value to nullopt, as
   // if it was not set in the first place.
   if (installer_package_id.has_value() &&
-      installer_package_id->app_type() == apps::AppType::kUnknown) {
+      installer_package_id->package_type() == apps::PackageType::kUnknown) {
     installer_package_id = std::nullopt;
   }
 
@@ -1372,24 +1372,14 @@
 crosapi::mojom::PackageIdType
 StructTraits<crosapi::mojom::PackageIdDataView, apps::PackageId>::package_type(
     const apps::PackageId& r) {
-  switch (r.app_type()) {
-    case apps::AppType::kArc:
+  switch (r.package_type()) {
+    case apps::PackageType::kArc:
       return crosapi::mojom::PackageIdType::kArc;
-    case apps::AppType::kWeb:
+    case apps::PackageType::kWeb:
       return crosapi::mojom::PackageIdType::kWeb;
-    case apps::AppType::kUnknown:
-    case apps::AppType::kBorealis:
-    case apps::AppType::kBruschetta:
-    case apps::AppType::kBuiltIn:
-    case apps::AppType::kChromeApp:
-    case apps::AppType::kCrostini:
-    case apps::AppType::kExtension:
-    case apps::AppType::kPluginVm:
-    case apps::AppType::kRemote:
-    case apps::AppType::kStandaloneBrowser:
-    case apps::AppType::kStandaloneBrowserChromeApp:
-    case apps::AppType::kStandaloneBrowserExtension:
-    case apps::AppType::kSystemWeb:
+    case apps::PackageType::kUnknown:
+    case apps::PackageType::kBorealis:
+    case apps::PackageType::kChromeApp:
       return crosapi::mojom::PackageIdType::kUnknown;
   }
 }
@@ -1397,25 +1387,25 @@
 bool StructTraits<crosapi::mojom::PackageIdDataView, apps::PackageId>::Read(
     crosapi::mojom::PackageIdDataView data,
     apps::PackageId* out) {
-  crosapi::mojom::PackageIdType package_type = data.package_type();
+  crosapi::mojom::PackageIdType mojom_package_type = data.package_type();
 
   std::string identifier;
   if (!data.ReadIdentifier(&identifier) || identifier.empty()) {
     return false;
   }
 
-  apps::AppType app_type = ([&package_type]() {
-    switch (package_type) {
+  apps::PackageType package_type = ([&mojom_package_type]() {
+    switch (mojom_package_type) {
       case crosapi::mojom::PackageIdType::kUnknown:
-        return apps::AppType::kUnknown;
+        return apps::PackageType::kUnknown;
       case crosapi::mojom::PackageIdType::kArc:
-        return apps::AppType::kArc;
+        return apps::PackageType::kArc;
       case crosapi::mojom::PackageIdType::kWeb:
-        return apps::AppType::kWeb;
+        return apps::PackageType::kWeb;
     }
   })();
 
-  *out = apps::PackageId(app_type, identifier);
+  *out = apps::PackageId(package_type, identifier);
 
   return true;
 }
diff --git a/chromeos/crosapi/mojom/app_service_types_mojom_traits_unittest.cc b/chromeos/crosapi/mojom/app_service_types_mojom_traits_unittest.cc
index 52c5ee18..f8c4ee8 100644
--- a/chromeos/crosapi/mojom/app_service_types_mojom_traits_unittest.cc
+++ b/chromeos/crosapi/mojom/app_service_types_mojom_traits_unittest.cc
@@ -73,7 +73,7 @@
   input->allow_close = true;
   input->allow_window_mode_selection = true;
   input->installer_package_id =
-      apps::PackageId(apps::AppType::kArc, "com.foo.bar");
+      apps::PackageId(apps::PackageType::kArc, "com.foo.bar");
 
   apps::AppPtr output;
   ASSERT_TRUE(
@@ -138,7 +138,7 @@
   EXPECT_TRUE(output->allow_close.value());
   EXPECT_TRUE(output->allow_window_mode_selection.value());
   EXPECT_EQ(output->installer_package_id,
-            apps::PackageId(apps::AppType::kArc, "com.foo.bar"));
+            apps::PackageId(apps::PackageType::kArc, "com.foo.bar"));
 }
 
 // Test that serialization and deserialization works with optional fields that
@@ -218,7 +218,8 @@
   auto input = std::make_unique<apps::App>(apps::AppType::kWeb, "abcdefg");
   // In practice, nobody should ever create an Unknown PackageId like this. The
   // most likely cause of this case is version skew in crosapi.
-  input->installer_package_id = apps::PackageId(apps::AppType::kUnknown, "foo");
+  input->installer_package_id =
+      apps::PackageId(apps::PackageType::kUnknown, "foo");
 
   apps::AppPtr output;
   ASSERT_TRUE(
@@ -1329,7 +1330,7 @@
 
 TEST(AppServiceTypesMojomTraitsTest, PackageIdRoundTrip) {
   {
-    auto package_id = apps::PackageId(apps::AppType::kArc, "com.foo.bar");
+    auto package_id = apps::PackageId(apps::PackageType::kArc, "com.foo.bar");
     apps::PackageId output;
     ASSERT_TRUE(mojo::test::SerializeAndDeserialize<crosapi::mojom::PackageId>(
         package_id, output));
@@ -1337,14 +1338,14 @@
   }
   {
     auto package_id =
-        apps::PackageId(apps::AppType::kWeb, "https://www.foo.com/bar");
+        apps::PackageId(apps::PackageType::kWeb, "https://www.foo.com/bar");
     apps::PackageId output;
     ASSERT_TRUE(mojo::test::SerializeAndDeserialize<crosapi::mojom::PackageId>(
         package_id, output));
     EXPECT_EQ(package_id, output);
   }
   {
-    auto package_id = apps::PackageId(apps::AppType::kUnknown, "someapp");
+    auto package_id = apps::PackageId(apps::PackageType::kUnknown, "someapp");
     apps::PackageId output;
     ASSERT_TRUE(mojo::test::SerializeAndDeserialize<crosapi::mojom::PackageId>(
         package_id, output));
diff --git a/chromeos/crosapi/mojom/file_system_provider.mojom b/chromeos/crosapi/mojom/file_system_provider.mojom
index cb59f98..ea68799 100644
--- a/chromeos/crosapi/mojom/file_system_provider.mojom
+++ b/chromeos/crosapi/mojom/file_system_provider.mojom
@@ -103,9 +103,16 @@
 };
 
 [Stable]
+struct CloudFileInfo {
+  string? version_tag@0;
+};
+
+[Stable]
 struct FSPChange {
+  // Next MinVersion: 2
   FSPChangeType type@0;
   mojo_base.mojom.FilePath path@1;
+  [MinVersion=1] CloudFileInfo? cloud_file_info@2;
 };
 
 [Stable, Extensible]
diff --git a/chromeos/crosapi/mojom/prefs.mojom b/chromeos/crosapi/mojom/prefs.mojom
index 778e7c4a..62ff4152 100644
--- a/chromeos/crosapi/mojom/prefs.mojom
+++ b/chromeos/crosapi/mojom/prefs.mojom
@@ -133,6 +133,8 @@
   [MinVersion=17] kDnsOverHttpsEffectiveTemplatesChromeOS = 45,
   // M125: prefs::kAccessibilityReducedAnimationsEnabled
   [MinVersion=18] kAccessibilityReducedAnimationsEnabled = 46,
+  // M125: ash::prefs::kMahiEnabled
+  [MinVersion=19] kMahiEnabled = 47,
 };
 
 // Information about who or what is controlling a particular pref. This is used
diff --git a/chromeos/services/network_config/public/mojom/cros_network_config.mojom b/chromeos/services/network_config/public/mojom/cros_network_config.mojom
index d64c463a..45839c2 100644
--- a/chromeos/services/network_config/public/mojom/cros_network_config.mojom
+++ b/chromeos/services/network_config/public/mojom/cros_network_config.mojom
@@ -1071,6 +1071,9 @@
 // configuration as a whole instead of individual network configurations.
 // Default values should be explicitly provided for boolean values.
 struct GlobalPolicy {
+  // If true, users will be allowed to modify APNs.
+  bool allow_apn_modification = true;
+
   // If true, allow PIN locking of SIMs attached to the device.
   // If false, PIN locking is prohibited.
   bool allow_cellular_sim_lock = true;
diff --git a/clank b/clank
index d1f7d33..682118a 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit d1f7d3377bd9fd9b5fb158dd6ae77f92498ae428
+Subproject commit 682118afe56ac594ac2a4343274a9bed1dac8710
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp
index d553570d..8bd95db 100644
--- a/components/autofill_payments_strings.grdp
+++ b/components/autofill_payments_strings.grdp
@@ -1053,9 +1053,12 @@
 
   <!-- Autofill IBAN preferences -->
   <if expr="is_android">
-    <message name="IDS_AUTOFILL_ADD_LOCAL_IBAN" desc="Button that allows the user to add a new IBAN that will be saved locally." formatter_data="android_java">
+    <message name="IDS_AUTOFILL_ADD_LOCAL_IBAN" desc="Button in the Payment methods page that allows the user to add a new IBAN that will be saved locally. It is also the title for the local IBAN editor when a user is adding a new IBAN." formatter_data="android_java">
       Add IBAN
     </message>
+    <message name="IDS_AUTOFILL_EDIT_LOCAL_IBAN" desc="Title for the local IBAN editor when a user is editing an existing IBAN." formatter_data="android_java">
+      Edit IBAN
+    </message>
   </if>
 
   <!-- Touch to fill bottom sheet -->
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_EDIT_LOCAL_IBAN.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_EDIT_LOCAL_IBAN.png.sha1
new file mode 100644
index 0000000..c3ec1a41
--- /dev/null
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_EDIT_LOCAL_IBAN.png.sha1
@@ -0,0 +1 @@
+5ac278a534ac6b22d0fb1642b1362418d75d276b
\ No newline at end of file
diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn
index 5f12a867..6e73470 100644
--- a/components/browser_ui/styles/android/BUILD.gn
+++ b/components/browser_ui/styles/android/BUILD.gn
@@ -22,7 +22,6 @@
 
 android_resources("java_resources") {
   sources = [
-    "java/res/color-night/toolbar_icon_unfocused_activity_tint_list.xml",
     "java/res/color/chip_text_color_secondary_list.xml",
     "java/res/color/color_on_surface_with_alpha_10.xml",
     "java/res/color/color_primary_with_alpha_10.xml",
diff --git a/components/browser_ui/styles/android/java/res/color-night/toolbar_icon_unfocused_activity_tint_list.xml b/components/browser_ui/styles/android/java/res/color-night/toolbar_icon_unfocused_activity_tint_list.xml
deleted file mode 100644
index 723d93db..0000000
--- a/components/browser_ui/styles/android/java/res/color-night/toolbar_icon_unfocused_activity_tint_list.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-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.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:alpha="0.55" android:color="?attr/colorOnSurface" />
-</selector>
\ No newline at end of file
diff --git a/components/browser_ui/styles/android/java/res/color/toolbar_icon_unfocused_activity_tint_list.xml b/components/browser_ui/styles/android/java/res/color/toolbar_icon_unfocused_activity_tint_list.xml
index 627ee96..0d869ef 100644
--- a/components/browser_ui/styles/android/java/res/color/toolbar_icon_unfocused_activity_tint_list.xml
+++ b/components/browser_ui/styles/android/java/res/color/toolbar_icon_unfocused_activity_tint_list.xml
@@ -5,5 +5,5 @@
 found in the LICENSE file.
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:alpha="0.65" android:color="?attr/colorOnSecondaryContainer" />
+    <item android:alpha="@dimen/default_disabled_alpha" android:color="@macro/default_icon_color" />
 </selector>
\ No newline at end of file
diff --git a/components/commerce/core/commerce_info_cache.h b/components/commerce/core/commerce_info_cache.h
index df93d3f..ad13c4c 100644
--- a/components/commerce/core/commerce_info_cache.h
+++ b/components/commerce/core/commerce_info_cache.h
@@ -21,6 +21,7 @@
 
 namespace commerce {
 
+struct PriceInsightsInfo;
 struct ProductInfo;
 
 class CommerceInfoCache {
@@ -41,8 +42,12 @@
 
     std::unique_ptr<base::CancelableOnceClosure> run_local_extraction_task;
 
-    // The product info associated with the URL.
+    // The product info associated with the URL or nullptr if not available.
     std::unique_ptr<ProductInfo> product_info;
+
+    // The price insights info associated with the URL or nullptr if not
+    // available.
+    std::unique_ptr<PriceInsightsInfo> price_insights_info;
   };
 
   CommerceInfoCache();
diff --git a/components/commerce/core/compare/BUILD.gn b/components/commerce/core/compare/BUILD.gn
index f342eea..7c5a8f33 100644
--- a/components/commerce/core/compare/BUILD.gn
+++ b/components/commerce/core/compare/BUILD.gn
@@ -4,6 +4,8 @@
 
 source_set("compare") {
   sources = [
+    "cluster_manager.cc",
+    "cluster_manager.h",
     "product_specifications_server_proxy.cc",
     "product_specifications_server_proxy.h",
   ]
@@ -24,6 +26,7 @@
     "//net/traffic_annotation:traffic_annotation",
     "//services/data_decoder/public/cpp",
     "//services/network/public/cpp:cpp",
+    "//url",
   ]
 }
 
diff --git a/components/commerce/core/compare/cluster_manager.cc b/components/commerce/core/compare/cluster_manager.cc
new file mode 100644
index 0000000..a79707f
--- /dev/null
+++ b/components/commerce/core/compare/cluster_manager.cc
@@ -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.
+
+#include "components/commerce/core/compare/cluster_manager.h"
+
+namespace commerce {
+
+ClusterManager::ClusterManager() = default;
+
+ClusterManager::~ClusterManager() = default;
+
+void ClusterManager::WebWrapperDestroyed(const GURL& url) {}
+
+void ClusterManager::DidNavigatePrimaryMainFrame(const GURL& url) {}
+
+void ClusterManager::DidNavigateAway(const GURL& new_url,
+                                     const GURL& from_url) {}
+
+}  // namespace commerce
diff --git a/components/commerce/core/compare/cluster_manager.h b/components/commerce/core/compare/cluster_manager.h
new file mode 100644
index 0000000..3356b52
--- /dev/null
+++ b/components/commerce/core/compare/cluster_manager.h
@@ -0,0 +1,37 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_COMMERCE_CORE_COMPARE_CLUSTER_MANAGER_H_
+#define COMPONENTS_COMMERCE_CORE_COMPARE_CLUSTER_MANAGER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "url/gurl.h"
+
+namespace commerce {
+
+// Class for clustering product information.
+class ClusterManager {
+ public:
+  ClusterManager();
+  ~ClusterManager();
+  ClusterManager(const ClusterManager&) = delete;
+  ClusterManager& operator=(const ClusterManager&) = delete;
+
+  // A notification that a WebWrapper with `url` has been destroyed. This
+  // signals that the web page backing the provided WebWrapper is about to be
+  // destroyed. Typically corresponds to a user closing a tab.
+  void WebWrapperDestroyed(const GURL& url);
+  // A notification that a web wrapper with `url` finished a navigation in the
+  // primary main frame.
+  void DidNavigatePrimaryMainFrame(const GURL& url);
+  // A notification that the user navigated away from `from_url` to `new_url`.
+  void DidNavigateAway(const GURL& new_url, const GURL& from_url);
+
+ private:
+  base::WeakPtrFactory<ClusterManager> weak_ptr_factory_{this};
+};
+
+}  // namespace commerce
+
+#endif  // COMPONENTS_COMMERCE_CORE_COMPARE_CLUSTER_MANAGER_H_
diff --git a/components/commerce/core/shopping_service.cc b/components/commerce/core/shopping_service.cc
index 36bf06c..6154ad9 100644
--- a/components/commerce/core/shopping_service.cc
+++ b/components/commerce/core/shopping_service.cc
@@ -24,6 +24,7 @@
 #include "components/commerce/core/commerce_constants.h"
 #include "components/commerce/core/commerce_feature_list.h"
 #include "components/commerce/core/commerce_utils.h"
+#include "components/commerce/core/compare/cluster_manager.h"
 #include "components/commerce/core/compare/product_specifications_server_proxy.h"
 #include "components/commerce/core/discounts_storage.h"
 #include "components/commerce/core/feature_utils.h"
@@ -79,12 +80,6 @@
 
 const uint64_t kProductInfoLocalExtractionDelayMs = 2000;
 
-ProductInfoCacheEntry::ProductInfoCacheEntry() = default;
-ProductInfoCacheEntry::~ProductInfoCacheEntry() = default;
-
-PriceInsightsInfoCacheEntry::PriceInsightsInfoCacheEntry() = default;
-PriceInsightsInfoCacheEntry::~PriceInsightsInfoCacheEntry() = default;
-
 ShoppingService::ShoppingService(
     const std::string& country_on_startup,
     const std::string& locale_on_startup,
@@ -223,6 +218,8 @@
   product_specs_server_proxy_ =
       std::make_unique<ProductSpecificationsServerProxy>(
           account_checker_.get(), identity_manager, url_loader_factory);
+
+  cluster_manager_ = std::make_unique<ClusterManager>();
 }
 
 AccountChecker* ShoppingService::GetAccountChecker() {
@@ -236,6 +233,7 @@
 void ShoppingService::DidNavigatePrimaryMainFrame(WebWrapper* web) {
   HandleDidNavigatePrimaryMainFrameForProductInfo(web);
   HandleDidNavigatePrimaryMainFrameForPriceInsightsInfo(web);
+  cluster_manager_->DidNavigatePrimaryMainFrame(web->GetLastCommittedURL());
 }
 
 void ShoppingService::HandleDidNavigatePrimaryMainFrameForProductInfo(
@@ -247,7 +245,10 @@
     return;
   }
 
-  UpdateProductInfoCacheForInsertion(web->GetLastCommittedURL());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (IsProductInfoApiEnabled()) {
+    commerce_info_cache_.AddRef(web->GetLastCommittedURL());
+  }
 
   opt_guide_->CanApplyOptimization(
       web->GetLastCommittedURL(),
@@ -275,8 +276,9 @@
 }
 
 void ShoppingService::DidNavigateAway(WebWrapper* web, const GURL& from_url) {
-  UpdateProductInfoCacheForRemoval(from_url);
-  UpdatePriceInsightsInfoCacheForRemoval(from_url);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  commerce_info_cache_.RemoveRef(web->GetLastCommittedURL());
+  cluster_manager_->DidNavigateAway(web->GetLastCommittedURL(), from_url);
 }
 
 void ShoppingService::DidStopLoading(WebWrapper* web) {
@@ -491,18 +493,10 @@
 }
 
 void ShoppingService::WebWrapperDestroyed(WebWrapper* web) {
-  open_web_wrappers_.erase(web);
-  UpdateProductInfoCacheForRemoval(web->GetLastCommittedURL());
-  UpdatePriceInsightsInfoCacheForRemoval(web->GetLastCommittedURL());
-}
-
-void ShoppingService::UpdateProductInfoCacheForInsertion(const GURL& url) {
-  if (!IsProductInfoApiEnabled())
-    return;
-
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  commerce_info_cache_.AddRef(url);
+  open_web_wrappers_.erase(web);
+  commerce_info_cache_.RemoveRef(web->GetLastCommittedURL());
+  cluster_manager_->WebWrapperDestroyed(web->GetLastCommittedURL());
 }
 
 void ShoppingService::UpdateProductInfoCache(
@@ -531,12 +525,6 @@
   return entry->product_info.get();
 }
 
-void ShoppingService::UpdateProductInfoCacheForRemoval(const GURL& url) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  commerce_info_cache_.RemoveRef(url);
-}
-
 void ShoppingService::PDPMetricsCallback(
     bool is_off_the_record,
     optimization_guide::OptimizationGuideDecision decision,
@@ -1246,10 +1234,10 @@
       std::optional<PriceInsightsInfo> optional_info;
       optional_info.emplace(*info);
 
-      auto it = price_insights_info_cache_.find(url.spec());
-      if (kPriceInsightsUseCache.Get() &&
-          it != price_insights_info_cache_.end()) {
-        it->second->info = std::move(info);
+      CommerceInfoCache::CacheEntry* entry =
+          commerce_info_cache_.GetEntryForUrl(url);
+      if (kPriceInsightsUseCache.Get() && entry) {
+        entry->price_insights_info = std::move(info);
       }
 
       std::move(callback).Run(url, std::move(optional_info));
@@ -1258,11 +1246,11 @@
   }
 
   // Check cache if we don't get info back from OptGuide.
-  auto it = price_insights_info_cache_.find(url.spec());
-  if (kPriceInsightsUseCache.Get() && it != price_insights_info_cache_.end() &&
-      it->second->info) {
+  CommerceInfoCache::CacheEntry* entry =
+      commerce_info_cache_.GetEntryForUrl(url);
+  if (entry && entry->price_insights_info) {
     std::optional<PriceInsightsInfo> optional_info;
-    optional_info.emplace(*(it->second->info));
+    optional_info.emplace(*(entry->price_insights_info));
     std::move(callback).Run(url, std::move(optional_info));
   } else {
     std::move(callback).Run(url, std::nullopt);
@@ -1348,12 +1336,6 @@
   }
 
   auto url = web->GetLastCommittedURL().spec();
-  if (price_insights_info_cache_.find(url) ==
-      price_insights_info_cache_.end()) {
-    price_insights_info_cache_.emplace(
-        url, std::make_unique<PriceInsightsInfoCacheEntry>());
-  }
-  price_insights_info_cache_[url]->pages_with_url_open++;
 
   opt_guide_->CanApplyOptimization(
       web->GetLastCommittedURL(),
@@ -1377,17 +1359,6 @@
           web->GetWeakPtr()));
 }
 
-void ShoppingService::UpdatePriceInsightsInfoCacheForRemoval(const GURL& url) {
-  auto it = price_insights_info_cache_.find(url.spec());
-  if (it != price_insights_info_cache_.end()) {
-    if (it->second->pages_with_url_open <= 1) {
-      price_insights_info_cache_.erase(it);
-    } else {
-      it->second->pages_with_url_open--;
-    }
-  }
-}
-
 void ShoppingService::HandleOptGuideShoppingPageTypesResponse(
     const GURL& url,
     IsShoppingPageCallback callback,
diff --git a/components/commerce/core/shopping_service.h b/components/commerce/core/shopping_service.h
index 79078f4..304e33d7 100644
--- a/components/commerce/core/shopping_service.h
+++ b/components/commerce/core/shopping_service.h
@@ -108,6 +108,7 @@
 }  // namespace metrics
 
 class BookmarkUpdateManager;
+class ClusterManager;
 class DiscountsStorage;
 class ParcelsManager;
 class ProductSpecificationsServerProxy;
@@ -120,47 +121,6 @@
 enum class SubscriptionType;
 struct CommerceSubscription;
 
-// A struct that keeps track of cached product info related data about a url.
-struct ProductInfoCacheEntry {
- public:
-  ProductInfoCacheEntry();
-  ProductInfoCacheEntry(const ProductInfoCacheEntry&) = delete;
-  ProductInfoCacheEntry& operator=(const ProductInfoCacheEntry&) = delete;
-  ~ProductInfoCacheEntry();
-
-  // The number of pages that have the URL open.
-  size_t pages_with_url_open{0};
-
-  // Whether the fallback local extraction needs to run for page.
-  bool needs_local_extraction_run{false};
-
-  // The time that the local extraction execution started. This is primarily
-  // used for metrics.
-  base::Time local_extraction_execution_start_time;
-
-  std::unique_ptr<base::CancelableOnceClosure> run_local_extraction_task;
-
-  // The product info associated with the URL.
-  std::unique_ptr<ProductInfo> product_info;
-};
-
-// A struct that keeps track of cached price insights info related data about a
-// url.
-struct PriceInsightsInfoCacheEntry {
- public:
-  PriceInsightsInfoCacheEntry();
-  PriceInsightsInfoCacheEntry(const PriceInsightsInfoCacheEntry&) = delete;
-  PriceInsightsInfoCacheEntry& operator=(const PriceInsightsInfoCacheEntry&) =
-      delete;
-  ~PriceInsightsInfoCacheEntry();
-
-  // The number of pages that have the URL open.
-  size_t pages_with_url_open{0};
-
-  // The price insights info associated with the URL.
-  std::unique_ptr<PriceInsightsInfo> info;
-};
-
 // Types of shopping pages from backend.
 enum class ShoppingPageType {
   kUnknown = 0,
@@ -603,9 +563,6 @@
       optimization_guide::OptimizationGuideDecision decision,
       const optimization_guide::OptimizationMetadata& metadata);
 
-  // Update the cache notifying that a tab is on the specified URL.
-  void UpdateProductInfoCacheForInsertion(const GURL& url);
-
   // Update the data stored in the cache.
   void UpdateProductInfoCache(const GURL& url,
                               bool needs_js,
@@ -614,10 +571,6 @@
   // Get the data stored in the cache or nullptr if none exists.
   const ProductInfo* GetFromProductInfoCache(const GURL& url);
 
-  // Update the cache storing product info for a navigation away from the
-  // provided URL or closing of a tab.
-  void UpdateProductInfoCacheForRemoval(const GURL& url);
-
   // Whether APIs like |GetPriceInsightsInfoForURL| are enabled and allowed to
   // be used.
   bool IsPriceInsightsInfoApiEnabled();
@@ -637,10 +590,6 @@
   // Handle main frame navigation for the price insights info API.
   void HandleDidNavigatePrimaryMainFrameForPriceInsightsInfo(WebWrapper* web);
 
-  // Update the cache storing price insights info for a navigation away from the
-  // provided URL or closing of a tab.
-  void UpdatePriceInsightsInfoCacheForRemoval(const GURL& url);
-
   void HandleOptGuideShoppingPageTypesResponse(
       const GURL& url,
       IsShoppingPageCallback callback,
@@ -733,11 +682,6 @@
 
   std::unique_ptr<ProductSpecificationsServerProxy> product_specs_server_proxy_;
 
-  // This is a cache that maps URL to a cache entry that may or may not contain
-  // price insights info.
-  std::unordered_map<std::string, std::unique_ptr<PriceInsightsInfoCacheEntry>>
-      price_insights_info_cache_;
-
   std::unique_ptr<BookmarkUpdateManager> bookmark_update_manager_;
 
   // The object tracking metrics that are recorded at specific intervals.
@@ -763,6 +707,9 @@
   // selected tab (not necessarily navigation).
   std::vector<UrlInfo> recently_visited_tabs_;
 
+  // Class for clustering products.
+  std::unique_ptr<ClusterManager> cluster_manager_;
+
   // TODO(crbug.com/40067058): Delete this when ConsentLevel::kSync is deleted.
   //     See ConsentLevel::kSync documentation for details.
   base::ScopedObservation<syncer::SyncService, syncer::SyncServiceObserver>
diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc
index 5105003..0887cfb 100644
--- a/components/content_settings/core/browser/content_settings_registry.cc
+++ b/components/content_settings/core/browser/content_settings_registry.cc
@@ -96,7 +96,8 @@
   Register(ContentSettingsType::IMAGES, "images", CONTENT_SETTING_ALLOW,
            WebsiteSettingsInfo::SYNCABLE,
            /*allowlisted_primary_schemes=*/
-           {kChromeUIScheme, kChromeDevToolsScheme, kExtensionScheme},
+           {kChromeUIScheme, kChromeDevToolsScheme, kExtensionScheme,
+            kChromeUIUntrustedScheme},
            /*valid_settings=*/{CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK},
            WebsiteSettingsInfo::TOP_ORIGIN_WITH_RESOURCE_EXCEPTIONS_SCOPE,
            WebsiteSettingsRegistry::DESKTOP,
@@ -106,7 +107,8 @@
   Register(ContentSettingsType::JAVASCRIPT, "javascript", CONTENT_SETTING_ALLOW,
            WebsiteSettingsInfo::SYNCABLE,
            /*allowlisted_primary_schemes=*/
-           {kChromeUIScheme, kChromeDevToolsScheme, kExtensionScheme},
+           {kChromeUIScheme, kChromeDevToolsScheme, kExtensionScheme,
+            kChromeUIUntrustedScheme},
            /*valid_settings=*/{CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK},
            WebsiteSettingsInfo::TOP_ORIGIN_WITH_RESOURCE_EXCEPTIONS_SCOPE,
            WebsiteSettingsRegistry::DESKTOP |
diff --git a/components/content_settings/core/browser/content_settings_utils.h b/components/content_settings/core/browser/content_settings_utils.h
index 35d28bf..f2ef19e 100644
--- a/components/content_settings/core/browser/content_settings_utils.h
+++ b/components/content_settings/core/browser/content_settings_utils.h
@@ -46,6 +46,7 @@
 const char kChromeDevToolsScheme[] = "devtools";
 const char kChromeUIScheme[] = "chrome";
 const char kExtensionScheme[] = "chrome-extension";
+const char kChromeUIUntrustedScheme[] = "chrome-untrusted";
 
 std::string ContentSettingToString(ContentSetting setting);
 
diff --git a/components/content_settings/core/browser/host_content_settings_map.cc b/components/content_settings/core/browser/host_content_settings_map.cc
index 0aad75f..6f57af0 100644
--- a/components/content_settings/core/browser/host_content_settings_map.cc
+++ b/components/content_settings/core/browser/host_content_settings_map.cc
@@ -117,7 +117,8 @@
 bool SchemeCanBeAllowlisted(const std::string& scheme) {
   return scheme == content_settings::kChromeDevToolsScheme ||
          scheme == content_settings::kExtensionScheme ||
-         scheme == content_settings::kChromeUIScheme;
+         scheme == content_settings::kChromeUIScheme ||
+         scheme == content_settings::kChromeUIUntrustedScheme;
 }
 
 // Handles inheritance of settings from the regular profile into the incognito
diff --git a/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java
index 1147decd..5465e9f 100644
--- a/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java
+++ b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java
@@ -8,6 +8,8 @@
 import org.jni_zero.JNINamespace;
 import org.jni_zero.NativeMethods;
 
+import org.chromium.base.UserDataHost;
+
 /**
  * Java side of the JNI bridge between DataSharingServiceImpl in Java and C++. All method calls are
  * delegated to the native C++ class.
@@ -16,6 +18,8 @@
 public class DataSharingServiceImpl implements DataSharingService {
     private long mNativePtr;
 
+    private final UserDataHost mUserDataHost = new UserDataHost();
+
     @CalledByNative
     private static DataSharingServiceImpl create(long nativePtr) {
         return new DataSharingServiceImpl(nativePtr);
@@ -35,9 +39,15 @@
         return DataSharingServiceImplJni.get().getNetworkLoader(mNativePtr);
     }
 
+    @Override
+    public UserDataHost getUserDataHost() {
+        return mUserDataHost;
+    }
+
     @CalledByNative
     private void clearNativePtr() {
         mNativePtr = 0;
+        mUserDataHost.destroy();
     }
 
     @NativeMethods
diff --git a/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java
index b5c4243..80c5552e 100644
--- a/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java
+++ b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java
@@ -4,6 +4,8 @@
 
 package org.chromium.components.data_sharing;
 
+import org.chromium.base.UserDataHost;
+
 /**
  * DataSharingService is the core class for managing data sharing. It represents a native
  * DataSharingService object in Java.
@@ -20,4 +22,9 @@
 
     /** Returns the network loader for sending out network calls to backend services. */
     DataSharingNetworkLoader getNetworkLoader();
+
+    /**
+     * @return {@link UserDataHost} that manages {@link UserData} objects attached to.
+     */
+    UserDataHost getUserDataHost();
 }
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index 136c1f8..72cb2dc 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -187,9 +187,9 @@
           case ui::SHOW_STATE_FULLSCREEN:
           case ui::SHOW_STATE_INACTIVE:
           case ui::SHOW_STATE_END:
-            NOTREACHED() << " unknown state :"
-                         << window->GetProperty(
-                                aura::client::kRestoreShowStateKey);
+            DUMP_WILL_BE_NOTREACHED_NORETURN()
+                << " unknown state :"
+                << window->GetProperty(aura::client::kRestoreShowStateKey);
             return false;
         }
         break;
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
index 6319c807..a397a88d 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
@@ -618,10 +618,7 @@
             Intent targetIntent,
             GURL browserFallbackUrl,
             boolean canLaunchExternalFallback) {
-        if (browserFallbackUrl.isEmpty()
-                || (params.getRedirectHandler().isOnNavigation()
-                        // For instance, if this is a chained fallback URL, we ignore it.
-                        && params.getRedirectHandler().shouldNotOverrideUrlLoading())) {
+        if (browserFallbackUrl.isEmpty()) {
             return OverrideUrlLoadingResult.forNoOverride();
         }
 
@@ -665,14 +662,6 @@
             }
         }
 
-        // NOTE: any further redirection from fall-back URL should not override URL loading.
-        // Otherwise, it can be used in chain for fingerprinting multiple app installation
-        // status in one shot. In order to prevent this scenario, we notify redirection
-        // handler that redirection from the current navigation should stay in this app.
-        if (params.getRedirectHandler().isOnNavigation()) {
-            params.getRedirectHandler().setShouldNotOverrideUrlLoadingOnCurrentRedirectChain();
-        }
-
         if (debug()) Log.i(TAG, "redirecting to fallback URL");
         return OverrideUrlLoadingResult.forNavigateTab(browserFallbackUrl, params);
     }
@@ -1117,7 +1106,14 @@
             ExternalNavigationParams params,
             Intent targetIntent,
             GURL browserFallbackUrl,
-            @NavigationChainResult int navigationChainResult) {
+            @NavigationChainResult int navigationChainResult,
+            boolean isExternalProtocol) {
+        if (isExternalProtocol) {
+            // https://crbug.com/330555390. In order to avoid a fingerprinting vector, if an
+            // external protocol fails to launch an app due to the app not being installed, future
+            // navigations on the same redirect chain should also stay in Chrome.
+            params.getRedirectHandler().setShouldNotOverrideUrlLoadingOnCurrentRedirectChain();
+        }
         if (navigationChainResult != NavigationChainResult.ALLOWED) {
             return OverrideUrlLoadingResult.forNoOverride();
         }
@@ -1445,7 +1441,7 @@
             final GURL fallbackUrl) {
         if (shouldLaunch) {
             try {
-                startActivity(intent);
+                startActivity(intent, params);
                 if (params.getRequiredAsyncActionTakenCallback() != null) {
                     params.getRequiredAsyncActionTakenCallback()
                             .onResult(
@@ -1663,7 +1659,11 @@
 
         if (resolvingInfos.get().isEmpty()) {
             return handleUnresolvableIntent(
-                    params, targetIntent, browserFallbackUrl, navigationChainResult);
+                    params,
+                    targetIntent,
+                    browserFallbackUrl,
+                    navigationChainResult,
+                    isExternalProtocol);
         }
 
         if (resolvesToNonExportedActivity(resolvingInfos.get())) {
@@ -1739,12 +1739,12 @@
 
         return startActivity(
                 targetIntent,
+                params,
                 requiresIntentChooser,
                 resolvingInfos,
                 resolveActivity,
                 browserFallbackUrl,
-                intentTargetUrl,
-                params);
+                intentTargetUrl);
     }
 
     // https://crbug.com/1249964
@@ -1873,7 +1873,7 @@
             return OverrideUrlLoadingResult.forAsyncAction(
                     OverrideUrlLoadingAsyncActionType.UI_GATING_INTENT_LAUNCH);
         } else {
-            startActivity(intent);
+            startActivity(intent, params);
             if (debug()) Log.i(TAG, "Intent to Play Store.");
             return OverrideUrlLoadingResult.forExternalIntent();
         }
@@ -1943,7 +1943,7 @@
         Intent webApkIntent = new Intent(targetIntent);
         webApkIntent.setPackage(packageName);
         try {
-            startActivity(webApkIntent);
+            startActivity(webApkIntent, params);
             if (debug()) Log.i(TAG, "Launched WebAPK");
             return true;
         } catch (ActivityNotFoundException e) {
@@ -2078,36 +2078,42 @@
 
     /**
      * Start an activity for the intent. Used for intents that must be handled externally.
+     *
      * @param intent The intent we want to send.
      */
-    private void startActivity(Intent intent) {
-        startActivity(intent, false, null, null, null, null, null);
+    private void startActivity(Intent intent, ExternalNavigationParams params) {
+        startActivity(intent, params, false, null, null, null, null);
     }
 
     /**
      * Start an activity for the intent. Used for intents that may be handled internally or
      * externally.
+     *
      * @param intent The intent we want to send.
+     * @param params The ExternalNavigationParams for the navigation.
      * @param requiresIntentChooser Whether, for security reasons, the Intent Chooser is required to
-     *                              be shown.
-     *
-     * Below parameters are only used if |requiresIntentChooser| is true.
-     *
+     *     be shown.
+     *     <p>Below parameters are only used if |requiresIntentChooser| is true.
      * @param resolvingInfos The queryIntentActivities |intent| matches against.
      * @param resolveActivity The resolving Activity |intent| matches against.
      * @param browserFallbackUrl The fallback URL if the user chooses not to leave this app.
      * @param intentTargetUrl The URL |intent| is targeting.
-     * @param params The ExternalNavigationParams for the navigation.
      * @returns The OverrideUrlLoadingResult for starting (or not starting) the Activity.
      */
     protected OverrideUrlLoadingResult startActivity(
             Intent intent,
+            ExternalNavigationParams params,
             boolean requiresIntentChooser,
             QueryIntentActivitiesSupplier resolvingInfos,
             ResolveActivitySupplier resolveActivity,
             GURL browserFallbackUrl,
-            GURL intentTargetUrl,
-            ExternalNavigationParams params) {
+            GURL intentTargetUrl) {
+        // https://crbug.com/330555390. If we've launched an app on the current redirect chain, we
+        // should never launch a second one.
+        if (params.getRedirectHandler().isOnNavigation()) {
+            params.getRedirectHandler().setShouldNotOverrideUrlLoadingOnCurrentRedirectChain();
+        }
+
         // Only touches disk on Kitkat. See http://crbug.com/617725 for more context.
         StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
         try {
@@ -2276,7 +2282,7 @@
                                 // as a package.
                                 intent.setSelector(null);
                                 intent.setPackage(data.getComponent().getPackageName());
-                                startActivity(intent);
+                                startActivity(intent, params);
                                 callback.onResult(
                                         AsyncActionTakenParams.forExternalIntentLaunched(
                                                 true, params));
@@ -2364,7 +2370,7 @@
                         .with(
                                 MessageBannerProperties.ON_PRIMARY_ACTION,
                                 () -> {
-                                    startActivity(targetIntent);
+                                    startActivity(targetIntent, params);
                                     var callback = params.getRequiredAsyncActionTakenCallback();
                                     if (callback != null) {
                                         callback.onResult(
diff --git a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
index 9255241..1d3cc0e 100644
--- a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
+++ b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
@@ -1689,24 +1689,21 @@
         checkUrl(INTENT_URL_WITH_CHAIN_FALLBACK_URL, redirectHandler)
                 .expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
 
-        // As a result of intent resolution fallback, we have clobberred the current tab.
-        // The fall-back URL was HTTP-schemed, but it was effectively redirected to a new intent
-        // URL using javascript. However, we do not allow chained fallback intent, so we do NOT
-        // override URL loading here.
+        mDelegate.setCanResolveActivityForExternalSchemes(true);
+        // As a result of intent resolution fallback, we have clobberred the current tab and the
+        // sending site has learned that an app is not installed. In order to prevent chaining this
+        // and learning about more not-installed apps, even URLs that would otherwise successfully
+        // launch an app will use the fallback URL.
         redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 0, false, true);
         checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandler)
-                .expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
-
-        // Now enough time (2 seconds) have passed.
-        // New URL loading should not be affected.
-        // (The URL happened to be the same as previous one.)
-        // TODO(changwan): this is not likely cause flakiness, but it may be better to refactor
-        // systemclock or pass the new time as parameter.
-        long lastUserInteractionTimeInMillis = SystemClock.elapsedRealtime() + 2 * 1000L;
-        redirectHandler.updateNewUrlLoading(
-                PageTransition.LINK, false, true, lastUserInteractionTimeInMillis, 1, false, true);
-        checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandler)
                 .expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
+
+        // New user gesture.
+        redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
+        checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandler)
+                .expecting(
+                        OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
+                        START_OTHER_ACTIVITY);
     }
 
     @Test
@@ -3046,23 +3043,23 @@
         @Override
         protected OverrideUrlLoadingResult startActivity(
                 Intent intent,
+                ExternalNavigationParams params,
                 boolean requiresIntentChooser,
                 QueryIntentActivitiesSupplier resolvingInfos,
                 ResolveActivitySupplier resolveActivity,
                 GURL browserFallbackUrl,
-                GURL intentDataUrl,
-                ExternalNavigationParams params) {
+                GURL intentDataUrl) {
             mStartActivityIntent = intent;
             mRequiresIntentChooser = requiresIntentChooser;
             if (mSendIntentsForReal) {
                 return super.startActivity(
                         intent,
+                        params,
                         requiresIntentChooser,
                         resolvingInfos,
                         resolveActivity,
                         browserFallbackUrl,
-                        intentDataUrl,
-                        params);
+                        intentDataUrl);
             }
             return OverrideUrlLoadingResult.forExternalIntent();
         }
diff --git a/components/facilitated_payments/android/facilitated_payments_api_client_android.cc b/components/facilitated_payments/android/facilitated_payments_api_client_android.cc
index 7c40f6c..8e0aec9 100644
--- a/components/facilitated_payments/android/facilitated_payments_api_client_android.cc
+++ b/components/facilitated_payments/android/facilitated_payments_api_client_android.cc
@@ -58,7 +58,7 @@
 void FacilitatedPaymentsApiClientAndroid::InvokePurchaseAction(
     CoreAccountInfo primary_account,
     base::span<const uint8_t> action_token,
-    base::OnceCallback<void(bool)> callback) {
+    base::OnceCallback<void(PurchaseActionResult)> callback) {
   DCHECK(!IsAnyCallbackPending());
 
   purchase_action_callback_ = std::move(callback);
@@ -89,12 +89,19 @@
   }
 }
 
-void FacilitatedPaymentsApiClientAndroid::OnPurchaseActionResult(
+void FacilitatedPaymentsApiClientAndroid::OnPurchaseActionResultEnum(
     JNIEnv* env,
-    jboolean is_purchase_action_successful) {
-  if (purchase_action_callback_) {
-    std::move(purchase_action_callback_).Run(is_purchase_action_successful);
+    jint purchase_action_result) {
+  if (!purchase_action_callback_ ||
+      purchase_action_result <
+          static_cast<int>(PurchaseActionResult::kCouldNotInvoke) ||
+      purchase_action_result >
+          static_cast<int>(PurchaseActionResult::kResultCanceled)) {
+    return;
   }
+
+  std::move(purchase_action_callback_)
+      .Run(static_cast<PurchaseActionResult>(purchase_action_result));
 }
 
 bool FacilitatedPaymentsApiClientAndroid::IsAnyCallbackPending() const {
diff --git a/components/facilitated_payments/android/facilitated_payments_api_client_android.h b/components/facilitated_payments/android/facilitated_payments_api_client_android.h
index 3b19630..14f61af 100644
--- a/components/facilitated_payments/android/facilitated_payments_api_client_android.h
+++ b/components/facilitated_payments/android/facilitated_payments_api_client_android.h
@@ -42,16 +42,16 @@
   void IsAvailable(base::OnceCallback<void(bool)> callback) override;
   void GetClientToken(
       base::OnceCallback<void(std::vector<uint8_t>)> callback) override;
-  void InvokePurchaseAction(CoreAccountInfo primary_account,
-                            base::span<const uint8_t> action_token,
-                            base::OnceCallback<void(bool)> callback) override;
+  void InvokePurchaseAction(
+      CoreAccountInfo primary_account,
+      base::span<const uint8_t> action_token,
+      base::OnceCallback<void(PurchaseActionResult)> callback) override;
 
   void OnIsAvailable(JNIEnv* env, jboolean is_available);
   void OnGetClientToken(
       JNIEnv* env,
       const base::android::JavaRef<jbyteArray>& jclient_token_byte_array);
-  void OnPurchaseActionResult(JNIEnv* env,
-                              jboolean is_purchase_action_successful);
+  void OnPurchaseActionResultEnum(JNIEnv* env, jint purchase_action_result);
 
  private:
   bool IsAnyCallbackPending() const;
@@ -59,7 +59,7 @@
   base::android::ScopedJavaGlobalRef<jobject> java_bridge_;
   base::OnceCallback<void(bool)> is_available_callback_;
   base::OnceCallback<void(std::vector<uint8_t>)> get_client_token_callback_;
-  base::OnceCallback<void(bool)> purchase_action_callback_;
+  base::OnceCallback<void(PurchaseActionResult)> purchase_action_callback_;
 };
 
 }  // namespace payments::facilitated
diff --git a/components/facilitated_payments/android/facilitated_payments_api_client_android_unittest.cc b/components/facilitated_payments/android/facilitated_payments_api_client_android_unittest.cc
index f165e64a..9e8efd4 100644
--- a/components/facilitated_payments/android/facilitated_payments_api_client_android_unittest.cc
+++ b/components/facilitated_payments/android/facilitated_payments_api_client_android_unittest.cc
@@ -35,6 +35,14 @@
   *output = std::move(input);
 }
 
+void CaptureResultEnum(
+    bool* was_callback_invoked,
+    FacilitatedPaymentsApiClient::PurchaseActionResult* output,
+    FacilitatedPaymentsApiClient::PurchaseActionResult input) {
+  *was_callback_invoked = true;
+  *output = input;
+}
+
 TEST_F(FacilitatedPaymentsApiClientAndroidTest,
        IsAvailableResultIsFalseByDefault) {
   FacilitatedPaymentsApiClientAndroid apiClient(main_rfh());
@@ -65,17 +73,19 @@
        InvokePurchaseActionResultIsFalseByDefault) {
   FacilitatedPaymentsApiClientAndroid apiClient(main_rfh());
   bool was_callback_invoked = false;
-  bool purchase_action_result = false;
+  FacilitatedPaymentsApiClient::PurchaseActionResult purchase_action_result =
+      FacilitatedPaymentsApiClient::PurchaseActionResult::kResultOk;
   signin::IdentityTestEnvironment identity_test_environment;
 
   apiClient.InvokePurchaseAction(
       identity_test_environment.MakeAccountAvailable("test@example.test"),
       std::vector<uint8_t>{'A', 'c', 't', 'i', 'o', 'n'},
-      base::BindOnce(&CaptureBoolean, &was_callback_invoked,
+      base::BindOnce(&CaptureResultEnum, &was_callback_invoked,
                      &purchase_action_result));
 
   EXPECT_TRUE(was_callback_invoked);
-  EXPECT_FALSE(purchase_action_result);
+  EXPECT_EQ(FacilitatedPaymentsApiClient::PurchaseActionResult::kCouldNotInvoke,
+            purchase_action_result);
 }
 
 // Java bridge should invoke exactly one callback per method, but if it does
@@ -87,7 +97,9 @@
 
   apiClient.OnIsAvailable(env, false);
   apiClient.OnGetClientToken(env, nullptr);
-  apiClient.OnPurchaseActionResult(env, false);
+  apiClient.OnPurchaseActionResultEnum(
+      env, static_cast<jint>(
+               FacilitatedPaymentsApiClient::PurchaseActionResult::kResultOk));
 }
 
 }  // namespace
diff --git a/components/facilitated_payments/android/java/BUILD.gn b/components/facilitated_payments/android/java/BUILD.gn
index 3fe257ca..7fd7399 100644
--- a/components/facilitated_payments/android/java/BUILD.gn
+++ b/components/facilitated_payments/android/java/BUILD.gn
@@ -16,11 +16,21 @@
     "//build/android:build_java",
     "//components/signin/public/android:java",
     "//content/public/android:content_full_java",
+    "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/jni_zero:jni_zero_java",
   ]
-  srcjar_deps = [ ":jni_headers" ]
+  srcjar_deps = [
+    ":java_enums",
+    ":jni_headers",
+  ]
 }
 
 generate_jni("jni_headers") {
   sources = [ "src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientBridge.java" ]
 }
+
+java_cpp_enum("java_enums") {
+  sources = [
+    "../../core/browser/facilitated_payments_api_client.h"
+  ]
+}
diff --git a/components/facilitated_payments/android/java/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClient.java b/components/facilitated_payments/android/java/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClient.java
index 3048fff..dbeb87d 100644
--- a/components/facilitated_payments/android/java/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClient.java
+++ b/components/facilitated_payments/android/java/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClient.java
@@ -85,9 +85,18 @@
         default void onGetClientToken(byte[] clientToken) {}
 
         /**
+         * Notifies the delegate about the result of the facilitated payment.
+         *
+         * @param purchaseActionResult The result of the purchase action.
+         */
+        default void onPurchaseActionResultEnum(@PurchaseActionResult int purchaseActionResult) {}
+
+        /**
          * Notifies the delegate whether the facilitated payment was successful.
          *
          * @param isPurchaseActionSuccessful Whether the purchase action was successful.
+         *
+         * @Deprecated TODO(b/300335735): Remove this method.
          */
         default void onPurchaseActionResult(boolean isPurchaseActionSuccessful) {}
     }
@@ -147,7 +156,7 @@
      * @param actionToken An opaque token used for invoking the purchase action.
      */
     public void invokePurchaseAction(CoreAccountInfo primaryAccount, byte[] actionToken) {
-        mDelegate.onPurchaseActionResult(/* isPurchaseActionSuccessful= */ false);
+        mDelegate.onPurchaseActionResultEnum(PurchaseActionResult.COULD_NOT_INVOKE);
     }
 
     /**
@@ -158,6 +167,6 @@
      * @Deprecated TODO(https://crbug.com/329108444): Remove this method.
      */
     public void invokePurchaseAction(byte[] actionToken) {
-        mDelegate.onPurchaseActionResult(/* isPurchaseActionSuccessful= */ false);
+        mDelegate.onPurchaseActionResultEnum(PurchaseActionResult.COULD_NOT_INVOKE);
     }
 }
diff --git a/components/facilitated_payments/android/java/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientBridge.java b/components/facilitated_payments/android/java/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientBridge.java
index c3a1eb2..fdcefea 100644
--- a/components/facilitated_payments/android/java/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientBridge.java
+++ b/components/facilitated_payments/android/java/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientBridge.java
@@ -63,7 +63,7 @@
 
     /**
      * Initiates the payment flow UI by invoking the payment manager with the action token. The
-     * result is received back in the onPurchaseActionResult(boolean) method.
+     * result is received back in the onPurchaseActionResultEnum(PurchaseActionResult) method.
      *
      * @param primaryAccount User's signed in account.
      * @param actionToken An opaque token used for invoking the purchase action.
@@ -91,11 +91,10 @@
 
     // FacilitatedPaymentsApiClient.Delegate implementation:
     @Override
-    public void onPurchaseActionResult(boolean isPurchaseActionSuccessful) {
+    public void onPurchaseActionResultEnum(@PurchaseActionResult int purchaseActionResult) {
         if (mNativeFacilitatedPaymentsApiClientAndroid == 0) return;
-        FacilitatedPaymentsApiClientBridgeJni.get()
-                .onPurchaseActionResult(
-                        mNativeFacilitatedPaymentsApiClientAndroid, isPurchaseActionSuccessful);
+        FacilitatedPaymentsApiClientBridgeJni.get().onPurchaseActionResultEnum(
+                mNativeFacilitatedPaymentsApiClientAndroid, purchaseActionResult);
     }
 
     @NativeMethods
@@ -104,7 +103,7 @@
 
         void onGetClientToken(long nativeFacilitatedPaymentsApiClientAndroid, byte[] clientToken);
 
-        void onPurchaseActionResult(
-                long nativeFacilitatedPaymentsApiClientAndroid, boolean isPurchaseActionSuccessful);
+        void onPurchaseActionResultEnum(long nativeFacilitatedPaymentsApiClientAndroid,
+                @PurchaseActionResult int purchaseActionResult);
     }
 }
diff --git a/components/facilitated_payments/android/junit/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientBridgeUnitTest.java b/components/facilitated_payments/android/junit/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientBridgeUnitTest.java
index e39ff5d..707d4a3 100644
--- a/components/facilitated_payments/android/junit/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientBridgeUnitTest.java
+++ b/components/facilitated_payments/android/junit/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientBridgeUnitTest.java
@@ -84,8 +84,8 @@
                 /* primaryAccount= */ null, new byte[] {'A', 'c', 't', 'i', 'o', 'n'});
 
         verify(mBridgeNatives)
-                .onPurchaseActionResult(
-                        eq(NATIVE_FACILITATED_PAYMENTS_API_CLIENT_ANDROID), eq(false));
+                .onPurchaseActionResultEnum(eq(NATIVE_FACILITATED_PAYMENTS_API_CLIENT_ANDROID),
+                        eq(PurchaseActionResult.COULD_NOT_INVOKE));
     }
 
     @Test
diff --git a/components/facilitated_payments/android/junit/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientUnitTest.java b/components/facilitated_payments/android/junit/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientUnitTest.java
index 97a090da..f700d701 100644
--- a/components/facilitated_payments/android/junit/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientUnitTest.java
+++ b/components/facilitated_payments/android/junit/src/org/chromium/components/facilitated_payments/FacilitatedPaymentsApiClientUnitTest.java
@@ -13,6 +13,7 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Batch;
+import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.content_public.browser.RenderFrameHost;
 
 /** Tests for the facilitated payment API client. */
@@ -36,7 +37,7 @@
         public byte[] mClientToken;
 
         public boolean mIsPurchaseActionInvoked;
-        public boolean mIsPurchaseActionSuccessful;
+        public @PurchaseActionResult int mPurchaseActionResult;
 
         @Override
         public void onIsAvailable(boolean isAvailable) {
@@ -51,9 +52,9 @@
         }
 
         @Override
-        public void onPurchaseActionResult(boolean isPurchaseActionSuccessful) {
+        public void onPurchaseActionResultEnum(@PurchaseActionResult int purchaseActionResult) {
             mIsPurchaseActionInvoked = true;
-            mIsPurchaseActionSuccessful = isPurchaseActionSuccessful;
+            mPurchaseActionResult = purchaseActionResult;
         }
     }
 
@@ -87,10 +88,11 @@
         FacilitatedPaymentsApiClient apiClient =
                 FacilitatedPaymentsApiClient.create(/* renderFrameHost= */ null, delegate);
 
-        apiClient.invokePurchaseAction(new byte[] {'A', 'c', 't', 'i', 'o', 'n'});
+        apiClient.invokePurchaseAction(
+                /* primaryAccount= */ null, new byte[] {'A', 'c', 't', 'i', 'o', 'n'});
 
         Assert.assertTrue(delegate.mIsPurchaseActionInvoked);
-        Assert.assertFalse(delegate.mIsPurchaseActionSuccessful);
+        Assert.assertEquals(PurchaseActionResult.COULD_NOT_INVOKE, delegate.mPurchaseActionResult);
     }
 
     /** A fake implementation of the API client, which always succeeds. */
@@ -111,8 +113,8 @@
         }
 
         @Override
-        public void invokePurchaseAction(byte[] actionToken) {
-            mDelegate.onPurchaseActionResult(/* isPurchaseActionSuccessful= */ true);
+        public void invokePurchaseAction(CoreAccountInfo primaryAccount, byte[] actionToken) {
+            mDelegate.onPurchaseActionResultEnum(PurchaseActionResult.RESULT_OK);
         }
     }
 
@@ -158,10 +160,11 @@
         FacilitatedPaymentsApiClient apiClient =
                 FacilitatedPaymentsApiClient.create(/* renderFrameHost= */ null, delegate);
 
-        apiClient.invokePurchaseAction(new byte[] {'A', 'c', 't', 'i', 'o', 'n'});
+        apiClient.invokePurchaseAction(
+                /* primaryAccount= */ null, new byte[] {'A', 'c', 't', 'i', 'o', 'n'});
 
         Assert.assertTrue(delegate.mIsPurchaseActionInvoked);
-        Assert.assertTrue(delegate.mIsPurchaseActionSuccessful);
+        Assert.assertEquals(PurchaseActionResult.RESULT_OK, delegate.mPurchaseActionResult);
     }
 
     /**
diff --git a/components/facilitated_payments/core/browser/facilitated_payments_api_client.h b/components/facilitated_payments/core/browser/facilitated_payments_api_client.h
index ffb5081..15d95cf 100644
--- a/components/facilitated_payments/core/browser/facilitated_payments_api_client.h
+++ b/components/facilitated_payments/core/browser/facilitated_payments_api_client.h
@@ -35,6 +35,19 @@
 //                                        weak_ptr_factory_.GetWeakPtr()));
 class FacilitatedPaymentsApiClient {
  public:
+  // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.facilitated_payments
+  // The result of invoking the purchase manager with an action token.
+  enum class PurchaseActionResult : int {
+    // Could not invoke the purchase manager.
+    kCouldNotInvoke,
+
+    // The purchase manager was invoked successfully.
+    kResultOk,
+
+    // The user cancelled out of the purchase manager flow.
+    kResultCanceled,
+  };
+
   virtual ~FacilitatedPaymentsApiClient() = default;
 
   // Checks whether the facilitated payment API is available and invokes the
@@ -59,7 +72,7 @@
   virtual void InvokePurchaseAction(
       CoreAccountInfo primary_account,
       base::span<const uint8_t> action_token,
-      base::OnceCallback<void(bool)> callback) = 0;
+      base::OnceCallback<void(PurchaseActionResult)> callback) = 0;
 };
 
 }  // namespace payments::facilitated
diff --git a/components/facilitated_payments/core/browser/facilitated_payments_manager_unittest.cc b/components/facilitated_payments/core/browser/facilitated_payments_manager_unittest.cc
index e4eccbf..70ef298b 100644
--- a/components/facilitated_payments/core/browser/facilitated_payments_manager_unittest.cc
+++ b/components/facilitated_payments/core/browser/facilitated_payments_manager_unittest.cc
@@ -52,7 +52,7 @@
               InvokePurchaseAction,
               (CoreAccountInfo,
                base::span<const uint8_t>,
-               base::OnceCallback<void(bool)>),
+               base::OnceCallback<void(PurchaseActionResult)>),
               (override));
 };
 
diff --git a/components/feedback/redaction_tool/redaction_tool.cc b/components/feedback/redaction_tool/redaction_tool.cc
index 8b06836..77306ad5 100644
--- a/components/feedback/redaction_tool/redaction_tool.cc
+++ b/components/feedback/redaction_tool/redaction_tool.cc
@@ -1123,7 +1123,11 @@
 
   std::string fileName = filePath.substr(filePath.find_last_of("/\\") + 1);
 
-  if (fileName == "redaction_tool_unittest.cc") {
+  if (filePath.find("support_tool") != std::string::npos) {
+    return RedactionToolCaller::kSupportTool;
+  } else if (filePath.find("error_reporting") != std::string::npos) {
+    return RedactionToolCaller::kErrorReporting;
+  } else if (fileName == "redaction_tool_unittest.cc") {
     return RedactionToolCaller::kUnitTest;
   } else if (fileName == "system_log_uploader.cc") {
     return RedactionToolCaller::kSysLogUploader;
@@ -1133,13 +1137,8 @@
     return RedactionToolCaller::kBrowserSystemLogs;
   } else if (fileName == "feedback_common.cc") {
     return RedactionToolCaller::kFeedbackTool;
-  } else if (filePath.find("support_tool") != std::string::npos) {
-    return RedactionToolCaller::kSupportTool;
-  } else if (filePath.find("error_reporting") != std::string::npos) {
-    return RedactionToolCaller::kErrorReporting;
-  } else {
-    return RedactionToolCaller::kUnknown;
   }
+  return RedactionToolCaller::kUnknown;
 }
 
 std::string RedactionTool::RedactCustomPatternWithContext(
diff --git a/components/history_embeddings/history_embeddings_service.cc b/components/history_embeddings/history_embeddings_service.cc
index 7d4a8b2..4b5e364 100644
--- a/components/history_embeddings/history_embeddings_service.cc
+++ b/components/history_embeddings/history_embeddings_service.cc
@@ -16,6 +16,7 @@
 #include "base/time/time.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/history/core/browser/url_database.h"
+#include "components/history/core/browser/url_row.h"
 #include "components/history_embeddings/history_embeddings_features.h"
 #include "components/history_embeddings/mock_embedder.h"
 #include "components/history_embeddings/sql_database.h"
@@ -196,7 +197,9 @@
 void HistoryEmbeddingsService::OnHistoryDeletions(
     history::HistoryService* history_service,
     const history::DeletionInfo& deletion_info) {
-  // TODO(b/329495955): Implement actual cleanup of storage for this deletion.
+  storage_.AsyncCall(&Storage::HandleHistoryDeletions)
+      .WithArgs(deletion_info.IsAllHistory(), deletion_info.deleted_rows(),
+                deletion_info.deleted_visit_ids());
 }
 
 HistoryEmbeddingsService::Storage::Storage(const base::FilePath& storage_dir)
@@ -244,6 +247,24 @@
   return scored_urls;
 }
 
+void HistoryEmbeddingsService::Storage::HandleHistoryDeletions(
+    bool for_all_history,
+    history::URLRows deleted_rows,
+    std::set<history::VisitID> deleted_visit_ids) {
+  if (for_all_history) {
+    sql_database.DeleteAllData();
+    return;
+  }
+
+  for (history::URLRow url_row : deleted_rows) {
+    sql_database.DeleteDataForUrlId(url_row.id());
+  }
+
+  for (history::VisitID visit_id : deleted_visit_ids) {
+    sql_database.DeleteDataForVisitId(visit_id);
+  }
+}
+
 void HistoryEmbeddingsService::OnPassagesRetrieved(
     UrlPassages url_passages,
     std::vector<std::string> passages) {
@@ -276,11 +297,19 @@
 void HistoryEmbeddingsService::DeterminePassageVisibility(
     SearchResultCallback callback,
     std::vector<ScoredUrl> scored_urls) {
-  if (!page_content_annotations_service_ ||
-      !page_content_annotations_service_->GetModelInfoForType(
-          page_content_annotations::AnnotationType::kContentVisibility)) {
-    // Cannot calculate visibility, consider everything to not be visible.
-    std::move(callback).Run({});
+  bool is_visibility_model_available =
+      page_content_annotations_service_ &&
+      page_content_annotations_service_->GetModelInfoForType(
+          page_content_annotations::AnnotationType::kContentVisibility);
+  base::UmaHistogramCounts100("History.Embeddings.NumUrlsMatched",
+                              scored_urls.size());
+
+  base::UmaHistogramBoolean(
+      "History.Embeddings.VisibilityModelAvailableAtQuery",
+      is_visibility_model_available);
+  if (!is_visibility_model_available) {
+    OnPassageVisibilityCalculated(std::move(callback), std::move(scored_urls),
+                                  {});
     return;
   }
 
@@ -302,20 +331,32 @@
     std::vector<ScoredUrl> scored_urls,
     const std::vector<page_content_annotations::BatchAnnotationResult>&
         annotation_results) {
-  CHECK_EQ(scored_urls.size(), annotation_results.size());
+  if (annotation_results.empty()) {
+    scored_urls.clear();
+  } else {
+    CHECK_EQ(scored_urls.size(), annotation_results.size());
 
-  // Filter for scored URLs that are ok to be shown to the user.
-  auto urls_it = scored_urls.begin();
-  for (const page_content_annotations::BatchAnnotationResult& result :
-       annotation_results) {
-    if (result.visibility_score().value_or(0.0) <=
-        kContentVisibilityThreshold.Get()) {
-      urls_it = scored_urls.erase(urls_it);
-    } else {
-      ++urls_it;
+    // Filter for scored URLs that are ok to be shown to the user.
+    auto urls_it = scored_urls.begin();
+    for (const page_content_annotations::BatchAnnotationResult& result :
+         annotation_results) {
+      if (result.visibility_score().value_or(0.0) <=
+          kContentVisibilityThreshold.Get()) {
+        urls_it = scored_urls.erase(urls_it);
+      } else {
+        ++urls_it;
+      }
     }
   }
 
+  base::UmaHistogramCounts100("History.Embeddings.NumMatchedUrlsVisible",
+                              scored_urls.size());
+
+  if (scored_urls.empty()) {
+    std::move(callback).Run({});
+    return;
+  }
+
   // Use the callback task mechanism for simplicity and easier control with
   // other standard async machinery.
   history_service_->ScheduleDBTaskForUI(
diff --git a/components/history_embeddings/history_embeddings_service.h b/components/history_embeddings/history_embeddings_service.h
index 52bc3869..54209a25 100644
--- a/components/history_embeddings/history_embeddings_service.h
+++ b/components/history_embeddings/history_embeddings_service.h
@@ -12,6 +12,7 @@
 #include "base/files/file_path.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/sequence_bound.h"
 #include "components/history/core/browser/history_service.h"
@@ -80,8 +81,8 @@
 
  private:
   friend class HistoryEmbeddingsBrowserTest;
-  FRIEND_TEST_ALL_PREFIXES(HistoryEmbeddingsTest,
-                           ConstructsAndComputesEmbeddings);
+  friend class HistoryEmbeddingsServiceTest;
+  FRIEND_TEST_ALL_PREFIXES(HistoryEmbeddingsServiceTest, OnHistoryDeletions);
 
   // A utility container to wrap anything that should be accessed on
   // the separate storage worker sequence.
@@ -99,6 +100,11 @@
         Embedding query_embedding,
         size_t count);
 
+    // Handles the History deletions on the worker thread.
+    void HandleHistoryDeletions(bool for_all_history,
+                                history::URLRows deleted_rows,
+                                std::set<history::VisitID> deleted_visit_ids);
+
     // A VectorDatabase implementation that holds data in memory.
     VectorDatabaseInMemory vector_database;
 
diff --git a/components/history_embeddings/history_embeddings_service_unittest.cc b/components/history_embeddings/history_embeddings_service_unittest.cc
index 314733d..736337e 100644
--- a/components/history_embeddings/history_embeddings_service_unittest.cc
+++ b/components/history_embeddings/history_embeddings_service_unittest.cc
@@ -5,21 +5,36 @@
 #include "components/history_embeddings/history_embeddings_service.h"
 
 #include <memory>
+#include <optional>
 
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "base/time/time.h"
 #include "components/history/core/browser/history_service.h"
+#include "components/history/core/browser/history_types.h"
 #include "components/history/core/test/history_service_test_util.h"
+#include "components/history_embeddings/history_embeddings_features.h"
+#include "components/history_embeddings/vector_database.h"
 #include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
+#include "components/os_crypt/sync/os_crypt_mocker.h"
 #include "components/page_content_annotations/core/test_page_content_annotations_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace history_embeddings {
 
-class HistoryEmbeddingsTest : public testing::Test {
+class HistoryEmbeddingsServiceTest : public testing::Test {
  public:
   void SetUp() override {
+    feature_list_.InitAndEnableFeature(kHistoryEmbeddings);
+
+    OSCryptMocker::SetUp();
+
     CHECK(history_dir_.CreateUniqueTempDir());
 
     history_service_ =
@@ -35,9 +50,31 @@
     CHECK(page_content_annotations_service_);
   }
 
-  void TearDown() override {}
+  void TearDown() override { OSCryptMocker::TearDown(); }
+
+  size_t CountEmbeddingsRows(HistoryEmbeddingsService* service) {
+    size_t result = 0;
+    base::RunLoop loop;
+    service->storage_.PostTaskWithThisObject(base::BindLambdaForTesting(
+        [&](HistoryEmbeddingsService::Storage* storage) {
+          std::unique_ptr<SqlDatabase::EmbeddingsIterator> iterator =
+              storage->sql_database.MakeEmbeddingsIterator();
+          if (!iterator) {
+            return;
+          }
+          while (const UrlEmbeddings* item = iterator->Next()) {
+            result++;
+          }
+
+          loop.Quit();
+        }));
+    loop.Run();
+    return result;
+  }
 
  protected:
+  base::test::ScopedFeatureList feature_list_;
+
   base::test::TaskEnvironment task_environment_{
       base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME};
 
@@ -49,7 +86,7 @@
       page_content_annotations_service_;
 };
 
-TEST_F(HistoryEmbeddingsTest, ConstructsAndInvalidatesWeakPtr) {
+TEST_F(HistoryEmbeddingsServiceTest, ConstructsAndInvalidatesWeakPtr) {
   auto service = std::make_unique<HistoryEmbeddingsService>(
       history_service_.get(), page_content_annotations_service_.get());
   auto weak_ptr = service->AsWeakPtr();
@@ -58,4 +95,52 @@
   EXPECT_FALSE(weak_ptr);
 }
 
+TEST_F(HistoryEmbeddingsServiceTest, OnHistoryDeletions) {
+  auto add_page = [&](const std::string& url) {
+    history_service_->AddPage(GURL(url), base::Time::Now() - base::Days(4), 0,
+                              0, GURL(), history::RedirectList(),
+                              ui::PAGE_TRANSITION_LINK, history::SOURCE_BROWSED,
+                              false);
+  };
+  add_page("http://test1.com");
+  add_page("http://test2.com");
+  add_page("http://test3.com");
+
+  auto service = std::make_unique<HistoryEmbeddingsService>(
+      history_service_.get(), page_content_annotations_service_.get());
+
+  // Add a fake set of passages for all visits.
+  UrlPassages url_passages(/*url_id=*/1, /*visit_id=*/1, base::Time::Now());
+  std::vector<std::string> passages = {"test passage 1", "test passage 2"};
+  std::vector<Embedding> passages_embeddings = {
+      Embedding({1.0f, 2.0f, 3.0f, 4.0f}), Embedding({1.0f, 2.0f, 3.0f, 4.0f})};
+  service->OnPassagesEmbeddingsComputed(url_passages, passages,
+                                        passages_embeddings);
+  url_passages.url_id = 2;
+  url_passages.visit_id = 2;
+  service->OnPassagesEmbeddingsComputed(url_passages, passages,
+                                        passages_embeddings);
+  url_passages.url_id = 3;
+  url_passages.visit_id = 3;
+  service->OnPassagesEmbeddingsComputed(url_passages, passages,
+                                        passages_embeddings);
+
+  // Verify that we find all three passages initially.
+  EXPECT_EQ(CountEmbeddingsRows(service.get()), 3U);
+
+  // Verify that we can delete indivdiual URLs.
+  history_service_->DeleteURLs({GURL("http://test2.com")});
+  history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
+  EXPECT_EQ(CountEmbeddingsRows(service.get()), 2U);
+
+  // Verify that we can delete all of History at once.
+  base::CancelableTaskTracker tracker;
+  history_service_->ExpireHistoryBetween(
+      /*restrict_urls=*/{}, /*restrict_app_id=*/{},
+      /*begin_time=*/base::Time(), /*end_time=*/base::Time(),
+      /*user_initiated=*/true, base::BindLambdaForTesting([] {}), &tracker);
+  history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
+  EXPECT_EQ(CountEmbeddingsRows(service.get()), 0U);
+}
+
 }  // namespace history_embeddings
diff --git a/components/history_strings.grdp b/components/history_strings.grdp
index 1e6e948f..830b7ed2 100644
--- a/components/history_strings.grdp
+++ b/components/history_strings.grdp
@@ -134,11 +134,11 @@
   <message name="IDS_HISTORY_EMBEDDINGS_SUGGESTION_1" desc="On the history page, the text inside of a suggestion chip that a user can click to update their search to show results from yesterday" translateable="false">
     Yesterday
   </message>
-  <message name="IDS_HISTORY_EMBEDDINGS_SUGGESTION_2" desc="On the history page, the text inside of a suggestion chip that a user can click to update their search to show results from last week" translateable="false">
-    Last week
+  <message name="IDS_HISTORY_EMBEDDINGS_SUGGESTION_2" desc="On the history page, the text inside of a suggestion chip that a user can click to update their search to show results from 7 days" translateable="false">
+    Last 7 days
   </message>
-  <message name="IDS_HISTORY_EMBEDDINGS_SUGGESTION_3" desc="On the history page, the text inside of a suggestion chip that a user can click to update their search to show results from last month" translateable="false">
-    Last month
+  <message name="IDS_HISTORY_EMBEDDINGS_SUGGESTION_3" desc="On the history page, the text inside of a suggestion chip that a user can click to update their search to show results from 30 days" translateable="false">
+    Last 30 days
   </message>
   <message name="IDS_HISTORY_EMBEDDINGS_HEADING" desc="Heading for history embeddings results." translateable="false">
     Results for "<ph name="SEARCH_QUERY"><ex>pizza</ex>$1</ph>"
diff --git a/components/metrics/unsent_log_store.cc b/components/metrics/unsent_log_store.cc
index b840e49..f9ed0d0c 100644
--- a/components/metrics/unsent_log_store.cc
+++ b/components/metrics/unsent_log_store.cc
@@ -149,7 +149,7 @@
   DCHECK(!log_data.empty());
 
   if (!compression::GzipCompress(log_data, &compressed_log_data)) {
-    NOTREACHED();
+    DUMP_WILL_BE_NOTREACHED_NORETURN();
     return;
   }
 
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc
index 860d159..21e7d38b 100644
--- a/components/omnibox/browser/autocomplete_match.cc
+++ b/components/omnibox/browser/autocomplete_match.cc
@@ -52,6 +52,7 @@
 #endif
 
 constexpr bool kIsDesktop = !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS);
+constexpr bool kIsAndroid = BUILDFLAG(IS_ANDROID);
 
 namespace {
 
@@ -1379,7 +1380,7 @@
     return 0;
   }
 
-  if constexpr (!kIsDesktop) {
+  if constexpr (kIsAndroid) {
     if (IsClipboardType(type)) {
       return 0;
     }
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc b/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
index c699f96..675bbd6 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
@@ -229,16 +229,11 @@
   CHECK_EQ(reason, OnDeviceModelEligibilityReason::kSuccess);
 
   SessionImpl::OnDeviceOptions opts;
-  opts.start_session_fn =
-      base::BindRepeating(&OnDeviceModelServiceController::StartMojoSession,
-                          weak_ptr_factory_.GetWeakPtr(), model_paths);
-  opts.classify_text_safety_fn =
-      base::BindRepeating(&OnDeviceModelServiceController::ClassifyTextSafety,
-                          weak_ptr_factory_.GetWeakPtr(), model_paths);
+  opts.model_client = std::make_unique<OnDeviceModelClient>(
+      weak_ptr_factory_.GetWeakPtr(), model_paths);
   opts.model_versions.CopyFrom(model_versions_.value());
   opts.adapter = std::move(adapter);
-  opts.controller = weak_ptr_factory_.GetWeakPtr();
-  opts.safety_config = safety_config;
+  opts.safety_cfg = SafetyConfig(safety_config);
 
   return std::make_unique<SessionImpl>(
       feature, std::move(opts), std::move(execute_remote_fn),
@@ -279,21 +274,6 @@
   return model_remote_;
 }
 
-void OnDeviceModelServiceController::StartMojoSession(
-    on_device_model::ModelAssetPaths model_paths,
-    mojo::PendingReceiver<on_device_model::mojom::Session> session) {
-  GetOrCreateModelRemote(model_paths)->StartSession(std::move(session));
-}
-
-void OnDeviceModelServiceController::ClassifyTextSafety(
-    on_device_model::ModelAssetPaths model_paths,
-    const std::string& text,
-    on_device_model::mojom::OnDeviceModel::ClassifyTextSafetyCallback
-        callback) {
-  GetOrCreateModelRemote(model_paths)
-      ->ClassifyTextSafety(text, std::move(callback));
-}
-
 void OnDeviceModelServiceController::OnModelAssetsLoaded(
     mojo::PendingReceiver<on_device_model::mojom::OnDeviceModel> model,
     on_device_model::ModelAssets assets) {
@@ -412,11 +392,6 @@
   access_controller_->OnDisconnectedFromRemote();
 }
 
-bool OnDeviceModelServiceController::ShouldStartNewSession() const {
-  return access_controller_->ShouldStartNewSession() ==
-         OnDeviceModelEligibilityReason::kSuccess;
-}
-
 void OnDeviceModelServiceController::ShutdownServiceIfNoModelLoaded() {
   if (!model_remote_) {
     service_remote_.reset();
@@ -443,6 +418,38 @@
   return versions;
 }
 
+OnDeviceModelServiceController::OnDeviceModelClient::OnDeviceModelClient(
+    base::WeakPtr<OnDeviceModelServiceController> controller,
+    on_device_model::ModelAssetPaths model_paths)
+    : controller_(controller), model_paths_(model_paths) {}
+
+OnDeviceModelServiceController::OnDeviceModelClient::~OnDeviceModelClient() =
+    default;
+
+bool OnDeviceModelServiceController::OnDeviceModelClient::ShouldUse() {
+  return controller_ &&
+         controller_->access_controller_->ShouldStartNewSession() ==
+             OnDeviceModelEligibilityReason::kSuccess;
+}
+
+mojo::Remote<on_device_model::mojom::OnDeviceModel>&
+OnDeviceModelServiceController::OnDeviceModelClient::GetModelRemote() {
+  return controller_->GetOrCreateModelRemote(model_paths_);
+}
+
+void OnDeviceModelServiceController::OnDeviceModelClient::
+    OnResponseCompleted() {
+  if (controller_) {
+    controller_->access_controller_->OnResponseCompleted();
+  }
+}
+
+void OnDeviceModelServiceController::OnDeviceModelClient::OnSessionTimedOut() {
+  if (controller_) {
+    controller_->access_controller_->OnSessionTimedOut();
+  }
+}
+
 std::optional<proto::FeatureTextSafetyConfiguration>
 OnDeviceModelServiceController::SafetyModelInfo::GetConfig(
     proto::ModelExecutionFeature feature) const {
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller.h b/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
index 18e71b7..55e8b99 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
@@ -88,12 +88,6 @@
   void GetEstimatedPerformanceClass(
       GetEstimatedPerformanceClassCallback callback);
 
-  OnDeviceModelAccessController* access_controller(base::PassKey<SessionImpl>) {
-    return access_controller_.get();
-  }
-
-  bool ShouldStartNewSession() const;
-
   // Shuts down the service if there is no active model.
   void ShutdownServiceIfNoModelLoaded();
 
@@ -126,6 +120,23 @@
   }
 
  private:
+  class OnDeviceModelClient : public SessionImpl::OnDeviceModelClient {
+   public:
+    OnDeviceModelClient(
+        base::WeakPtr<OnDeviceModelServiceController> controller,
+        on_device_model::ModelAssetPaths model_paths);
+    ~OnDeviceModelClient() override;
+    bool ShouldUse() override;
+    mojo::Remote<on_device_model::mojom::OnDeviceModel>& GetModelRemote()
+        override;
+    void OnResponseCompleted() override;
+    void OnSessionTimedOut() override;
+
+   private:
+    base::WeakPtr<OnDeviceModelServiceController> controller_;
+    on_device_model::ModelAssetPaths model_paths_;
+  };
+  friend class OnDeviceModelClient;
   friend class base::RefCounted<OnDeviceModelServiceController>;
   friend class ChromeOnDeviceModelServiceController;
   friend class OnDeviceModelServiceControllerTest;
@@ -164,21 +175,10 @@
                     const std::string& component_version);
   void ClearModelPath();
 
+  // Ensures the service is running and provides a remote for the model.
   mojo::Remote<on_device_model::mojom::OnDeviceModel>& GetOrCreateModelRemote(
       on_device_model::ModelAssetPaths model_paths);
 
-  // Makes sure the service is running and starts a mojo session.
-  void StartMojoSession(
-      on_device_model::ModelAssetPaths model_paths,
-      mojo::PendingReceiver<on_device_model::mojom::Session> session);
-
-  // Invoke ClassifyTextSafety on the service.
-  void ClassifyTextSafety(
-      on_device_model::ModelAssetPaths model_paths,
-      const std::string& text,
-      on_device_model::mojom::OnDeviceModel::ClassifyTextSafetyCallback
-          callback);
-
   // Invoked at the end of model load, to continue with model execution.
   void OnLoadModelResult(on_device_model::mojom::LoadModelResult result);
 
diff --git a/components/optimization_guide/core/model_execution/session_impl.cc b/components/optimization_guide/core/model_execution/session_impl.cc
index dbea1bda..739bd09 100644
--- a/components/optimization_guide/core/model_execution/session_impl.cc
+++ b/components/optimization_guide/core/model_execution/session_impl.cc
@@ -164,12 +164,14 @@
   mojo::Receiver<on_device_model::mojom::ContextClient> client_{this};
 };
 
+SessionImpl::OnDeviceModelClient::~OnDeviceModelClient() = default;
+
 SessionImpl::OnDeviceOptions::OnDeviceOptions() = default;
 SessionImpl::OnDeviceOptions::OnDeviceOptions(OnDeviceOptions&&) = default;
 SessionImpl::OnDeviceOptions::~OnDeviceOptions() = default;
 
 bool SessionImpl::OnDeviceOptions::ShouldUse() const {
-  return controller && controller->ShouldStartNewSession();
+  return model_client->ShouldUse();
 }
 
 SessionImpl::SessionImpl(
@@ -392,8 +394,7 @@
   on_device_state_->start = base::TimeTicks::Now();
   on_device_state_->timer_for_first_response.Start(
       FROM_HERE, features::GetOnDeviceModelTimeForInitialResponse(),
-      base::BindOnce(&SessionImpl::DestroyOnDeviceStateAndFallbackToRemote,
-                     base::Unretained(this), ExecuteModelResult::kTimedOut));
+      base::BindOnce(&SessionImpl::OnSessionTimedOut, base::Unretained(this)));
 
   auto options = on_device_model::mojom::InputOptions::New();
   options->text = input->input_string;
@@ -402,10 +403,7 @@
   options->max_output_tokens = features::GetOnDeviceModelMaxTokensForOutput();
   options->top_k = sampling_params_.top_k;
   options->temperature = sampling_params_.temperature;
-  if (on_device_state_->opts.safety_config) {
-    options->safety_interval =
-        features::GetOnDeviceModelTextSafetyTokenInterval();
-  }
+  options->safety_interval = on_device_state_->opts.safety_cfg.TokenInterval();
 
   RunNextRequestSafetyCheckOrBeginExecution(std::move(options), 0);
 }
@@ -413,17 +411,14 @@
 void SessionImpl::RunNextRequestSafetyCheckOrBeginExecution(
     on_device_model::mojom::InputOptionsPtr options,
     int request_check_idx) {
-  if (!on_device_state_->opts.safety_config ||
-      on_device_state_->opts.safety_config->request_check_size() <=
-          request_check_idx) {
+  if (on_device_state_->opts.safety_cfg.NumRequestChecks() <=
+      request_check_idx) {
     // All check have passed.
     BeginRequestExecution(std::move(options));
     return;
   }
-  auto check_input = CreateSubstitutions(
-      *last_message_,
-      on_device_state_->opts.safety_config->request_check(request_check_idx)
-          .input_template());
+  auto check_input = on_device_state_->opts.safety_cfg.GetRequestCheckInput(
+      request_check_idx, *last_message_);
   if (!check_input) {
     // This is mostly likely means a malformed safety config.
     DestroyOnDeviceStateAndFallbackToRemote(
@@ -433,7 +428,7 @@
   proto::InternalOnDeviceModelExecutionInfo check_log;
   check_log.mutable_request()->mutable_text_safety_model_request()->set_text(
       check_input->input_string);
-  on_device_state_->opts.classify_text_safety_fn.Run(
+  on_device_state_->opts.model_client->GetModelRemote()->ClassifyTextSafety(
       check_input->input_string,
       base::BindOnce(&SessionImpl::OnRequestSafetyResult,
                      on_device_state_->session_weak_ptr_factory_.GetWeakPtr(),
@@ -447,12 +442,8 @@
     proto::InternalOnDeviceModelExecutionInfo check_log,
     on_device_model::mojom::SafetyInfoPtr safety_info) {
   // Evaluate the check.
-  const auto& check = on_device_state_->opts.safety_config->request_check(request_check_idx);
-  const auto& thresholds = check.safety_category_thresholds().empty()
-                               ? on_device_state_->opts.safety_config->safety_category_thresholds()
-                               : check.safety_category_thresholds();
-  bool is_unsafe = IsTextInUnsupportedOrUndeterminedLanguage(safety_info) ||
-                   HasUnsafeScores(thresholds, safety_info);
+  bool is_unsafe = on_device_state_->opts.safety_cfg.IsRequestUnsafe(
+      request_check_idx, safety_info);
 
   // Log the check execution.
   auto* response_log =
@@ -529,7 +520,8 @@
 
   // Only proceed to send the response if we are not evaluating text safety or
   // if there are text safety scores to evaluate.
-  if (!on_device_state_->opts.safety_config || chunk_provided_safety_info) {
+  if (!on_device_state_->opts.safety_cfg.IsMissingSafetyInfo(
+          chunk_provided_safety_info)) {
     SendResponse(ResponseType::kPartial);
   }
 }
@@ -545,12 +537,10 @@
       time_to_completion);
   on_device_state_->MutableLoggedResponse()->set_time_to_completion_millis(
       time_to_completion.InMilliseconds());
-  if (on_device_state_->opts.controller) {
-    on_device_state_->opts.controller->access_controller(/*pass_key=*/{})
-        ->OnResponseCompleted();
-  }
+  on_device_state_->opts.model_client->OnResponseCompleted();
 
-  if (on_device_state_->opts.safety_config && !summary->safety_info) {
+  if (on_device_state_->opts.safety_cfg.IsMissingSafetyInfo(
+          !!summary->safety_info)) {
     on_device_state_->receiver.ReportBadMessage(
         "Missing required safety scores on complete");
     CancelPendingResponse(
@@ -568,7 +558,7 @@
 on_device_model::mojom::Session& SessionImpl::GetOrCreateSession() {
   CHECK(ShouldUseOnDeviceModel());
   if (!on_device_state_->session) {
-    on_device_state_->opts.start_session_fn.Run(
+    on_device_state_->opts.model_client->GetModelRemote()->StartSession(
         on_device_state_->session.BindNewPipeAndPassReceiver());
     on_device_state_->session.set_disconnect_handler(
         base::BindOnce(&SessionImpl::OnDisconnect, base::Unretained(this)));
@@ -640,23 +630,20 @@
   auto redact_result =
       on_device_state_->opts.adapter->Redact(*last_message_, current_response);
   if (redact_result == RedactResult::kReject) {
-    if (on_device_state_->histogram_logger) {
-      on_device_state_->histogram_logger->set_result(
-          ExecuteModelResult::kContainedPII);
-      on_device_state_->histogram_logger.reset();
-    }
     logged_response->set_status(
         proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_RETRACTED);
-    CancelPendingResponse(ExecuteModelResult::kUsedOnDeviceOutputUnsafe,
+    CancelPendingResponse(ExecuteModelResult::kContainedPII,
                           ModelExecutionError::kFiltered);
     return;
   }
 
   const bool is_complete = response_type != ResponseType::kPartial;
   const bool is_unsupported_language =
-      IsTextInUnsupportedOrUndeterminedLanguage(
-          on_device_state_->current_safety_info);
-  const bool is_unsafe = IsUnsafeText(on_device_state_->current_safety_info);
+      on_device_state_->opts.safety_cfg
+          .IsTextInUnsupportedOrUndeterminedLanguage(
+              on_device_state_->current_safety_info);
+  const bool is_unsafe = on_device_state_->opts.safety_cfg.IsUnsafeText(
+      on_device_state_->current_safety_info);
   if (is_unsafe || is_complete) {
     on_device_state_->AddTextSafetyExecutionLogging(is_unsafe);
   }
@@ -667,7 +654,6 @@
     }
 
     if (features::GetOnDeviceModelRetractUnsafeContent()) {
-      on_device_state_->current_response.clear();
       CancelPendingResponse(ExecuteModelResult::kUsedOnDeviceOutputUnsafe,
                             is_unsupported_language
                                 ? ModelExecutionError::kUnsupportedLanguage
@@ -680,11 +666,6 @@
   auto output =
       on_device_state_->opts.adapter->ConstructOutputMetadata(current_response);
   if (!output) {
-    if (on_device_state_->histogram_logger) {
-      on_device_state_->histogram_logger->set_result(
-          ExecuteModelResult::kFailedConstructingResponseMessage);
-      on_device_state_->histogram_logger.reset();
-    }
     CancelPendingResponse(
         ExecuteModelResult::kFailedConstructingResponseMessage,
         ModelExecutionError::kGenericFailure);
@@ -694,7 +675,6 @@
   if (!is_complete &&
       on_device_state_->MutableLoggedResponse()->has_repeats()) {
     if (features::GetOnDeviceModelRetractRepeats()) {
-      on_device_state_->current_response.clear();
       logged_response->set_status(
           proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_RETRACTED);
       CancelPendingResponse(ExecuteModelResult::kResponseHadRepeats,
@@ -770,16 +750,16 @@
 }
 
 bool SessionImpl::ShouldUseOnDeviceModel() const {
-  return on_device_state_ && on_device_state_->opts.ShouldUse();
+  return on_device_state_ && on_device_state_->opts.model_client->ShouldUse();
+}
+
+void SessionImpl::OnSessionTimedOut() {
+  on_device_state_->opts.model_client->OnSessionTimedOut();
+  DestroyOnDeviceStateAndFallbackToRemote(ExecuteModelResult::kTimedOut);
 }
 
 void SessionImpl::DestroyOnDeviceStateAndFallbackToRemote(
     ExecuteModelResult result) {
-  if (result == ExecuteModelResult::kTimedOut &&
-      on_device_state_->opts.controller) {
-    on_device_state_->opts.controller->access_controller(/*pass_key=*/{})
-        ->OnSessionTimedOut();
-  }
   if (on_device_state_->histogram_logger) {
     on_device_state_->histogram_logger->set_result(result);
   }
@@ -873,27 +853,44 @@
   SendSuccessCompletionCallback(success_response_metadata);
 }
 
-bool SessionImpl::IsTextInUnsupportedOrUndeterminedLanguage(
+SafetyConfig::SafetyConfig() = default;
+SafetyConfig::SafetyConfig(
+    std::optional<proto::FeatureTextSafetyConfiguration> proto)
+    : proto_(proto) {}
+SafetyConfig::SafetyConfig(SafetyConfig&&) = default;
+SafetyConfig::~SafetyConfig() = default;
+SafetyConfig& SafetyConfig::operator=(SafetyConfig&&) = default;
+
+bool SafetyConfig::IsMissingSafetyInfo(bool has_safety_info) const {
+  return proto_ && !has_safety_info;
+}
+
+std::optional<uint32_t> SafetyConfig::TokenInterval() const {
+  if (!proto_) {
+    return std::nullopt;
+  }
+  return features::GetOnDeviceModelTextSafetyTokenInterval();
+}
+
+bool SafetyConfig::IsTextInUnsupportedOrUndeterminedLanguage(
     const on_device_model::mojom::SafetyInfoPtr& safety_info) const {
-  if (!on_device_state_->opts.safety_config) {
+  if (!proto_) {
     // No safety config, so no language requirements.
     return false;
   }
 
-  CHECK(on_device_state_->opts.safety_config);
-  if (on_device_state_->opts.safety_config->allowed_languages().empty()) {
+  if (proto_->allowed_languages().empty()) {
     // No language requirements.
     return false;
   }
 
-  CHECK(safety_info);
   if (!safety_info->language) {
     // No language detection available, but language detection is required.
     // Treat as an unsupported language.
     return true;
   }
 
-  if (!base::Contains(on_device_state_->opts.safety_config->allowed_languages(),
+  if (!base::Contains(proto_->allowed_languages(),
                       safety_info->language->code)) {
     // Unsupported language.
     return true;
@@ -909,17 +906,36 @@
   return false;
 }
 
-bool SessionImpl::IsUnsafeText(
+bool SafetyConfig::IsUnsafeText(
     const on_device_model::mojom::SafetyInfoPtr& safety_info) const {
-  if (!on_device_state_->opts.safety_config) {
+  if (!proto_) {
     // If no safety config and we are allowed here, that means we don't care
     // about the safety scores so just mark the content as safe.
     return false;
   }
+  return HasUnsafeScores(proto_->safety_category_thresholds(), safety_info);
+}
 
-  return HasUnsafeScores(
-      on_device_state_->opts.safety_config->safety_category_thresholds(),
-      safety_info);
+int SafetyConfig::NumRequestChecks() const {
+  return proto_ ? proto_->request_check_size() : 0;
+}
+
+std::optional<SubstitutionResult> SafetyConfig::GetRequestCheckInput(
+    int check_idx,
+    const google::protobuf::MessageLite& message) const {
+  return CreateSubstitutions(message,
+                             proto_->request_check(check_idx).input_template());
+}
+
+bool SafetyConfig::IsRequestUnsafe(
+    int check_idx,
+    const on_device_model::mojom::SafetyInfoPtr& safety_info) const {
+  const auto& check = proto_->request_check(check_idx);
+  const auto& thresholds = check.safety_category_thresholds().empty()
+                               ? proto_->safety_category_thresholds()
+                               : check.safety_category_thresholds();
+  return IsTextInUnsupportedOrUndeterminedLanguage(safety_info) ||
+         HasUnsafeScores(thresholds, safety_info);
 }
 
 SessionImpl::OnDeviceState::OnDeviceState(OnDeviceOptions&& options,
diff --git a/components/optimization_guide/core/model_execution/session_impl.h b/components/optimization_guide/core/model_execution/session_impl.h
index 817ddbafd..2bcbdec 100644
--- a/components/optimization_guide/core/model_execution/session_impl.h
+++ b/components/optimization_guide/core/model_execution/session_impl.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_SESSION_IMPL_H_
 #define COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_SESSION_IMPL_H_
 
+#include <memory>
 #include <optional>
 #include <string>
 #include <vector>
@@ -14,6 +15,7 @@
 #include "base/timer/timer.h"
 #include "components/optimization_guide/core/model_execution/feature_keys.h"
 #include "components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.h"
+#include "components/optimization_guide/core/model_execution/substitution.h"
 #include "components/optimization_guide/core/optimization_guide_model_executor.h"
 #include "components/optimization_guide/proto/model_quality_service.pb.h"
 #include "components/optimization_guide/proto/text_safety_model_metadata.pb.h"
@@ -25,7 +27,6 @@
 
 namespace optimization_guide {
 class OnDeviceModelFeatureAdapter;
-class OnDeviceModelServiceController;
 
 using ExecuteRemoteFn = base::RepeatingCallback<void(
     ModelBasedCapabilityKey feature,
@@ -33,33 +34,74 @@
     std::unique_ptr<proto::LogAiDataRequest>,
     OptimizationGuideModelExecutionResultCallback)>;
 
+class SafetyConfig final {
+ public:
+  SafetyConfig();
+  explicit SafetyConfig(std::optional<proto::FeatureTextSafetyConfiguration>);
+  SafetyConfig(SafetyConfig&&);
+  SafetyConfig& operator=(SafetyConfig&&);
+  ~SafetyConfig();
+
+  bool IsMissingSafetyInfo(bool has_safety_info) const;
+  std::optional<uint32_t> TokenInterval() const;
+
+  // Whether the text is in a language not supported by the safety classifier,
+  // or the language could not be detected despite the classifier requiring one
+  // or more specific languages.
+  bool IsTextInUnsupportedOrUndeterminedLanguage(
+      const on_device_model::mojom::SafetyInfoPtr& safety_info) const;
+
+  // Whether scores indicate the output text is unsafe.
+  bool IsUnsafeText(
+      const on_device_model::mojom::SafetyInfoPtr& safety_info) const;
+
+  // The number of request safety checks to perform.
+  int NumRequestChecks() const;
+
+  // Constructs input for a request safety check.
+  // check_idx must be < NumRequestChecks().
+  std::optional<SubstitutionResult> GetRequestCheckInput(
+      int check_idx,
+      const google::protobuf::MessageLite& request_metadata) const;
+
+  // Evaluates scores for a request safety check.
+  // check_idx must be < NumRequestChecks().
+  bool IsRequestUnsafe(
+      int check_idx,
+      const on_device_model::mojom::SafetyInfoPtr& safety_info) const;
+
+ private:
+  std::optional<proto::FeatureTextSafetyConfiguration> proto_;
+};
+
 // Session implementation that uses either the on device model or the server
 // model.
 class SessionImpl : public OptimizationGuideModelExecutor::Session,
                         public on_device_model::mojom::StreamingResponder {
  public:
-  using StartSessionFn = base::RepeatingCallback<void(
-      mojo::PendingReceiver<on_device_model::mojom::Session>)>;
-  using ClassifyTextSafetyFn = base::RepeatingCallback<void(
-      const std::string&,
-      on_device_model::mojom::OnDeviceModel::ClassifyTextSafetyCallback)>;
+  class OnDeviceModelClient {
+   public:
+    virtual ~OnDeviceModelClient() = 0;
+    // Called to check whether this client is still usable.
+    virtual bool ShouldUse() = 0;
+    // Called to retrieve connection the managed model.
+    virtual mojo::Remote<on_device_model::mojom::OnDeviceModel>&
+    GetModelRemote() = 0;
+    // Called to report a successful execution of the model.
+    virtual void OnResponseCompleted() = 0;
+    // Called to report a timeout reached while waiting for model response.
+    virtual void OnSessionTimedOut() = 0;
+  };
 
   struct OnDeviceOptions final {
     OnDeviceOptions();
     OnDeviceOptions(OnDeviceOptions&&);
     ~OnDeviceOptions();
 
-    using StartSessionFn = base::RepeatingCallback<void(
-        mojo::PendingReceiver<on_device_model::mojom::Session>)>;
-    using ClassifyTextSafetyFn = base::RepeatingCallback<void(
-        const std::string&,
-        on_device_model::mojom::OnDeviceModel::ClassifyTextSafetyCallback)>;
-    StartSessionFn start_session_fn;
-    ClassifyTextSafetyFn classify_text_safety_fn;
+    std::unique_ptr<OnDeviceModelClient> model_client;
     proto::OnDeviceModelVersions model_versions;
     scoped_refptr<const OnDeviceModelFeatureAdapter> adapter;
-    base::WeakPtr<OnDeviceModelServiceController> controller;
-    std::optional<proto::FeatureTextSafetyConfiguration> safety_config;
+    SafetyConfig safety_cfg;
 
     // Returns true if the on-device model may be used.
     bool ShouldUse() const;
@@ -242,6 +284,9 @@
   // Called when the connection to the service is dropped.
   void OnDisconnect();
 
+  // Called when a on-device response was not received within the timeout.
+  void OnSessionTimedOut();
+
   // Sends `current_response_` to the client.
   void SendResponse(ResponseType response_type);
 
@@ -282,16 +327,6 @@
   std::unique_ptr<google::protobuf::MessageLite> MergeContext(
       const google::protobuf::MessageLite& request);
 
-  // Whether the text is in a language not supported by the safety classifier,
-  // or the language could not be detected despite the classifier requiring one
-  // or more specific languages.
-  bool IsTextInUnsupportedOrUndeterminedLanguage(
-      const on_device_model::mojom::SafetyInfoPtr& safety_info) const;
-
-  // Whether the text is unsafe.
-  bool IsUnsafeText(
-      const on_device_model::mojom::SafetyInfoPtr& safety_info) const;
-
   // Sends the partial response callback.
   void SendPartialResponseCallback(const proto::Any& success_response_metadata);
 
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index 1c5fbca..750f8af 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit 1c5fbca24a4d277baf714dca2557d254cda56c28
+Subproject commit 750f8af0ac3d188eb13a742a04c00af449b62137
diff --git a/components/paint_preview/browser/paint_preview_client.cc b/components/paint_preview/browser/paint_preview_client.cc
index 9f1d05b..e6f1112 100644
--- a/components/paint_preview/browser/paint_preview_client.cc
+++ b/components/paint_preview/browser/paint_preview_client.cc
@@ -422,7 +422,7 @@
   if (!token.has_value()) {
     DVLOG(1) << "Error: Attempted to capture a frame without an "
                 "embedding token.";
-    NOTREACHED();
+    DUMP_WILL_BE_NOTREACHED_NORETURN();
     return;
   }
 
diff --git a/components/performance_manager/v8_memory/v8_context_tracker_browsertest.cc b/components/performance_manager/v8_memory/v8_context_tracker_browsertest.cc
index 16ecb61..8914f5f 100644
--- a/components/performance_manager/v8_memory/v8_context_tracker_browsertest.cc
+++ b/components/performance_manager/v8_memory/v8_context_tracker_browsertest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/location.h"
 #include "base/strings/stringprintf.h"
 #include "base/command_line.h"
 #include "components/performance_manager/execution_context/execution_context_registry_impl.h"
@@ -26,6 +27,13 @@
 namespace performance_manager {
 namespace v8_memory {
 
+struct ContextCounts {
+  size_t v8_context_count = 0;
+  size_t execution_context_count = 0;
+  size_t detached_v8_context_count = 0;
+  size_t destroyed_execution_context_count = 0;
+};
+
 class V8ContextTrackerTest : public PerformanceManagerBrowserTestHarness {
  public:
   using Super = PerformanceManagerBrowserTestHarness;
@@ -45,22 +53,62 @@
     Super::SetUpCommandLine(command_line);
   }
 
-  void ExpectCounts(size_t v8_context_count,
-                    size_t execution_context_count,
-                    size_t detached_v8_context_count,
-                    size_t destroyed_execution_context_count) {
+  void SetUpOnMainThread() override {
     RunInGraph([&](Graph* graph) {
       auto* v8ct = V8ContextTracker::GetFromGraph(graph);
       ASSERT_TRUE(v8ct);
-      EXPECT_EQ(v8_context_count, v8ct->GetV8ContextCountForTesting());
-      EXPECT_EQ(execution_context_count,
-                v8ct->GetExecutionContextCountForTesting());
-      EXPECT_EQ(detached_v8_context_count,
-                v8ct->GetDetachedV8ContextCountForTesting());
-      EXPECT_EQ(destroyed_execution_context_count,
-                v8ct->GetDestroyedExecutionContextCountForTesting());
+
+      // The browser could start with execution contexts and/or v8 contexts (for
+      // example if it creates a spare renderer loading about:blank, or
+      // something preloads a utility context).
+      current_counts_.v8_context_count = v8ct->GetV8ContextCountForTesting();
+      current_counts_.execution_context_count =
+          v8ct->GetExecutionContextCountForTesting();
+
+      // There should not be any detached or destroyed contexts on start.
+      EXPECT_EQ(v8ct->GetDetachedV8ContextCountForTesting(), 0u);
+      EXPECT_EQ(v8ct->GetDestroyedExecutionContextCountForTesting(), 0u);
+    });
+    Super::SetUpOnMainThread();
+  }
+
+  void ExpectCountIncrease(
+      ContextCounts count_change,
+      const base::Location& location = base::Location::Current()) {
+    RunInGraph([&](Graph* graph) {
+      SCOPED_TRACE(location.ToString());
+      auto* v8ct = V8ContextTracker::GetFromGraph(graph);
+      ASSERT_TRUE(v8ct);
+
+      // There may be extra V8 contexts created, such as for lazily-created
+      // utility contexts.
+      EXPECT_GE(
+          v8ct->GetV8ContextCountForTesting(),
+          current_counts_.v8_context_count + count_change.v8_context_count);
+      current_counts_.v8_context_count = v8ct->GetV8ContextCountForTesting();
+
+      EXPECT_EQ(v8ct->GetExecutionContextCountForTesting(),
+                current_counts_.execution_context_count +
+                    count_change.execution_context_count);
+      current_counts_.execution_context_count =
+          v8ct->GetExecutionContextCountForTesting();
+
+      EXPECT_EQ(v8ct->GetDetachedV8ContextCountForTesting(),
+                current_counts_.detached_v8_context_count +
+                    count_change.detached_v8_context_count);
+      current_counts_.detached_v8_context_count =
+          v8ct->GetDetachedV8ContextCountForTesting();
+
+      EXPECT_EQ(v8ct->GetDestroyedExecutionContextCountForTesting(),
+                current_counts_.destroyed_execution_context_count +
+                    count_change.destroyed_execution_context_count);
+      current_counts_.destroyed_execution_context_count =
+          v8ct->GetDestroyedExecutionContextCountForTesting();
     });
   }
+
+ private:
+  ContextCounts current_counts_;
 };
 
 // TODO(crbug.com/1482180): Re-enable on Mac.
@@ -70,9 +118,8 @@
 #define MAYBE_AboutBlank AboutBlank
 #endif
 IN_PROC_BROWSER_TEST_F(V8ContextTrackerTest, MAYBE_AboutBlank) {
-  ExpectCounts(0, 0, 0, 0);
   ASSERT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
-  ExpectCounts(1, 1, 0, 0);
+  ExpectCountIncrease({.v8_context_count = 1, .execution_context_count = 1});
 }
 
 IN_PROC_BROWSER_TEST_F(V8ContextTrackerTest, SameOriginIframeAttributionData) {
@@ -135,12 +182,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(V8ContextTrackerTest, SameSiteNavigation) {
-  ExpectCounts(0, 0, 0, 0);
   auto* contents = shell()->web_contents();
   GURL urla(embedded_test_server()->GetURL("a.com", "/a_embeds_b.html"));
   ASSERT_TRUE(
       NavigateAndWaitForConsoleMessage(contents, urla, "b.html loaded"));
-  ExpectCounts(2, 2, 0, 0);
+  ExpectCountIncrease({.v8_context_count = 2, .execution_context_count = 2});
 
   // Get pointers to the RFHs for each frame.
   content::RenderFrameHost* rfha = contents->GetPrimaryMainFrame();
@@ -159,27 +205,26 @@
   if (rfh_should_change) {
     // When RenderDocument is enabled, a new RenderFrameHost will be created for
     // the navigation to `urlb`. Both a new V8 context and ExecutionContext are
-    // created, and the old ExecutionContext is destroyed.
-    ExpectCounts(/*v8_context_count=*/3, /*execution_context_count=*/3,
-                 /*detached_v8_context_count=*/1,
-                 /*destroyed_execution_context_count=*/1);
+    // created, and the old ExecutionContext is destroyed..
+    ExpectCountIncrease({.v8_context_count = 1,
+                         .execution_context_count = 1,
+                         .detached_v8_context_count = 1,
+                         .destroyed_execution_context_count = 1});
   } else {
     // When RenderDocument is disabled, the same RenderFrameHost will be reused
     // for the navigation to `urlb`. So only a new V8 context will be created,
     // not a new ExecutionContext.
-    ExpectCounts(/*v8_context_count=*/3, /*execution_context_count=*/2,
-                 /*detached_v8_context_count=*/1,
-                 /*destroyed_execution_context_count=*/0);
+    ExpectCountIncrease(
+        {.v8_context_count = 1, .detached_v8_context_count = 1});
   }
 }
 
 IN_PROC_BROWSER_TEST_F(V8ContextTrackerTest, DetachedContext) {
-  ExpectCounts(0, 0, 0, 0);
   auto* contents = shell()->web_contents();
   GURL urla(embedded_test_server()->GetURL("a.com", "/a_embeds_a.html"));
   ASSERT_TRUE(
       NavigateAndWaitForConsoleMessage(contents, urla, "a.html loaded"));
-  ExpectCounts(2, 2, 0, 0);
+  ExpectCountIncrease({.v8_context_count = 2, .execution_context_count = 2});
 
   // Get pointers to the RFHs for each frame.
   content::RenderFrameHost* rfha = contents->GetPrimaryMainFrame();
@@ -192,7 +237,10 @@
                      "iframe.parentNode.removeChild(iframe); "
                      "console.log('detached and leaked iframe');"));
 
-  ExpectCounts(2, 2, 1, 1);
+  ExpectCountIncrease({
+      .detached_v8_context_count = 1,
+      .destroyed_execution_context_count = 1,
+  });
 }
 
 }  // namespace v8_memory
diff --git a/components/prefs/pref_service.cc b/components/prefs/pref_service.cc
index 99dbc5a..8eb33f2 100644
--- a/components/prefs/pref_service.cc
+++ b/components/prefs/pref_service.cc
@@ -321,7 +321,7 @@
 
 const base::Value& PrefService::GetValue(base::StringPiece path) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return *GetPreferenceValueChecked(path);
+  return *GetPreferenceValue(path);
 }
 
 const base::Value::Dict& PrefService::GetDict(base::StringPiece path) const {
@@ -591,7 +591,7 @@
       pref_service_(service) {}
 
 const base::Value* PrefService::Preference::GetValue() const {
-  return pref_service_->GetPreferenceValueChecked(name_);
+  return pref_service_->GetPreferenceValue(name_);
 }
 
 const base::Value* PrefService::Preference::GetRecommendedValue() const {
@@ -681,13 +681,6 @@
   return found_value;
 }
 
-const base::Value* PrefService::GetPreferenceValueChecked(
-    base::StringPiece path) const {
-  const base::Value* value = GetPreferenceValue(path);
-  DCHECK(value) << "Trying to read an unregistered pref: " << path;
-  return value;
-}
-
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void PrefService::SetStandaloneBrowserPref(const std::string& path,
                                            const base::Value& value) {
diff --git a/components/prefs/pref_service.h b/components/prefs/pref_service.h
index 5785725..4b42c6d9 100644
--- a/components/prefs/pref_service.h
+++ b/components/prefs/pref_service.h
@@ -517,7 +517,6 @@
   // value (GetValue() calls back though the preference service to
   // actually get the value.).
   const base::Value* GetPreferenceValue(base::StringPiece path) const;
-  const base::Value* GetPreferenceValueChecked(base::StringPiece path) const;
 
   const scoped_refptr<PrefRegistry> pref_registry_;
 
diff --git a/components/safe_browsing/core/browser/ping_manager.cc b/components/safe_browsing/core/browser/ping_manager.cc
index ed03ae2..28120c7c 100644
--- a/components/safe_browsing/core/browser/ping_manager.cc
+++ b/components/safe_browsing/core/browser/ping_manager.cc
@@ -11,15 +11,19 @@
 #include "base/check.h"
 #include "base/containers/contains.h"
 #include "base/containers/fixed_flat_set.h"
+#include "base/files/file_util.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
+#include "base/rand_util.h"
 #include "base/strings/escape.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/task/thread_pool.h"
 #include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
 #include "components/safe_browsing/core/browser/safe_browsing_hats_delegate.h"
 #include "components/safe_browsing/core/common/features.h"
@@ -63,6 +67,11 @@
   }
 }
 
+std::string GetRandFileName() {
+  return base::NumberToString(
+      base::RandGenerator(std::numeric_limits<uint64_t>::max()));
+}
+
 const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
     net::DefineNetworkTrafficAnnotation("safe_browsing_extended_reporting",
                                         R"(
@@ -104,6 +113,21 @@
 
 namespace safe_browsing {
 
+// SafeBrowsingPingManager::Persister implementation -----------------------
+
+PingManager::Persister::Persister(const base::FilePath& persister_root_path) {
+  dir_path_ = persister_root_path.AppendASCII("DownloadReports");
+}
+
+void PingManager::Persister::WriteReport(const std::string& serialized_report) {
+  base::File::Error error;
+  if (!base::CreateDirectoryAndGetError(dir_path_, &error)) {
+    return;
+  }
+  base::FilePath file_path = dir_path_.AppendASCII((GetRandFileName()));
+  base::WriteFile(file_path, serialized_report);
+}
+
 // SafeBrowsingPingManager implementation ----------------------------------
 
 // static
@@ -118,12 +142,13 @@
         get_user_population_callback,
     base::RepeatingCallback<ChromeUserPopulation::PageLoadToken(GURL)>
         get_page_load_token_callback,
-    std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate) {
+    std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate,
+    const base::FilePath& persister_root_path) {
   return std::make_unique<PingManager>(
       config, url_loader_factory, std::move(token_fetcher),
       get_should_fetch_access_token, webui_delegate, ui_task_runner,
       get_user_population_callback, get_page_load_token_callback,
-      std::move(hats_delegate));
+      std::move(hats_delegate), persister_root_path);
 }
 
 PingManager::PingManager(
@@ -137,7 +162,8 @@
         get_user_population_callback,
     base::RepeatingCallback<ChromeUserPopulation::PageLoadToken(GURL)>
         get_page_load_token_callback,
-    std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate)
+    std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate,
+    const base::FilePath& persister_root_path)
     : config_(config),
       url_loader_factory_(url_loader_factory),
       token_fetcher_(std::move(token_fetcher)),
@@ -146,7 +172,15 @@
       ui_task_runner_(ui_task_runner),
       get_user_population_callback_(get_user_population_callback),
       get_page_load_token_callback_(get_page_load_token_callback),
-      hats_delegate_(std::move(hats_delegate)) {}
+      hats_delegate_(std::move(hats_delegate)) {
+  persister_ = base::SequenceBound<Persister>(
+      base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+           base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}),
+      persister_root_path);
+  // TODO(crbug.com/329471668): Schedule tasks to read reports from persister
+  // and send them.
+}
 
 PingManager::~PingManager() {}
 
@@ -264,11 +298,20 @@
   return ReportThreatDetailsResult::SUCCESS;
 }
 
-PingManager::ReportThreatDetailsResult
+PingManager::PersistThreatDetailsResult
 PingManager::PersistThreatDetailsAndReportOnNextStartup(
     std::unique_ptr<ClientSafeBrowsingReportRequest> report) {
-  // TODO(crbug.com/329471668): Persist report on disk.
-  return ReportThreatDetailsResult::SUCCESS;
+  SanitizeThreatDetailsReport(report.get());
+  std::string serialized_report;
+  if (!report->SerializeToString(&serialized_report)) {
+    return PersistThreatDetailsResult::kSerializationError;
+  }
+  if (serialized_report.empty()) {
+    return PersistThreatDetailsResult::kEmptyReport;
+  }
+  persister_.AsyncCall(&Persister::WriteReport)
+      .WithArgs(std::move(serialized_report));
+  return PersistThreatDetailsResult::kPersistTaskPosted;
 }
 
 void PingManager::AttachThreatDetailsAndLaunchSurvey(
diff --git a/components/safe_browsing/core/browser/ping_manager.h b/components/safe_browsing/core/browser/ping_manager.h
index 6fd2fbf..d8b5341 100644
--- a/components/safe_browsing/core/browser/ping_manager.h
+++ b/components/safe_browsing/core/browser/ping_manager.h
@@ -12,9 +12,11 @@
 #include <string>
 
 #include "base/containers/unique_ptr_adapters.h"
+#include "base/files/file_util.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/threading/sequence_bound.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/safe_browsing/core/browser/db/hit_report.h"
 #include "components/safe_browsing/core/browser/db/util.h"
@@ -40,6 +42,16 @@
     EMPTY_REPORT = 2,
   };
 
+  enum class PersistThreatDetailsResult {
+    // The task to persist the report has posted. The actual file write
+    // operation may still fail.
+    kPersistTaskPosted = 0,
+    // There was a problem serializing the report to a string.
+    kSerializationError = 1,
+    // The report is empty, so it is not sent.
+    kEmptyReport = 2,
+  };
+
   // Interface via which a client of this class can surface relevant events in
   // WebUI. All methods must be called on the UI thread.
   class WebUIDelegate {
@@ -54,6 +66,23 @@
     virtual void AddToHitReportsSent(std::unique_ptr<HitReport> hit_report) = 0;
   };
 
+  // Helper class to read/write a report on disk.
+  class Persister {
+   public:
+    explicit Persister(const base::FilePath& persister_root_path);
+    Persister(const Persister&) = delete;
+    Persister& operator=(const Persister&) = delete;
+
+    ~Persister() = default;
+
+    // Writes |serialized_report| to a new file in |dir_path_|.
+    void WriteReport(const std::string& serialized_report);
+
+   private:
+    // The directory that the files will be written in.
+    base::FilePath dir_path_;
+  };
+
   explicit PingManager(
       const V4ProtocolConfig& config,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
@@ -65,7 +94,8 @@
           get_user_population_callback,
       base::RepeatingCallback<ChromeUserPopulation::PageLoadToken(GURL)>
           get_page_load_token_callback,
-      std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate);
+      std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate,
+      const base::FilePath& persister_root_path);
   PingManager(const PingManager&) = delete;
   PingManager& operator=(const PingManager&) = delete;
 
@@ -83,7 +113,8 @@
           get_user_population_callback,
       base::RepeatingCallback<ChromeUserPopulation::PageLoadToken(GURL)>
           get_page_load_token_callback,
-      std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate);
+      std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate,
+      const base::FilePath& persister_root_path);
 
   void OnURLLoaderComplete(network::SimpleURLLoader* source,
                            std::unique_ptr<std::string> response_body);
@@ -107,7 +138,7 @@
 
   // Similar to |ReportThreatDetails|, but persists the report on disk and sends
   // it on next startup.
-  virtual ReportThreatDetailsResult PersistThreatDetailsAndReportOnNextStartup(
+  virtual PersistThreatDetailsResult PersistThreatDetailsAndReportOnNextStartup(
       std::unique_ptr<ClientSafeBrowsingReportRequest> report);
 
   // Launches a survey and attaches ThreatDetails to the survey response.
@@ -188,6 +219,8 @@
   // Launches HaTS surveys.
   std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate_;
 
+  base::SequenceBound<Persister> persister_;
+
   base::WeakPtrFactory<PingManager> weak_factory_{this};
 };
 
diff --git a/components/safe_browsing/core/browser/ping_manager_unittest.cc b/components/safe_browsing/core/browser/ping_manager_unittest.cc
index 1646c4ae3..ff4b5d5 100644
--- a/components/safe_browsing/core/browser/ping_manager_unittest.cc
+++ b/components/safe_browsing/core/browser/ping_manager_unittest.cc
@@ -3,8 +3,10 @@
 // found in the LICENSE file.
 
 #include "components/safe_browsing/core/browser/ping_manager.h"
+
 #include "base/base64.h"
 #include "base/base64url.h"
+#include "base/files/file_enumerator.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
 #include "base/run_loop.h"
@@ -14,6 +16,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "base/test/test_file_util.h"
 #include "base/time/time.h"
 #include "base/values.h"
 #include "components/safe_browsing/core/browser/db/v4_test_util.h"
@@ -80,6 +83,7 @@
   std::string key_param_;
   std::unique_ptr<MockWebUIDelegate> webui_delegate_ =
       std::make_unique<MockWebUIDelegate>();
+  base::FilePath persister_root_path_;
   FakeSafeBrowsingHatsDelegate* SetUpHatsDelegate();
 
  private:
@@ -95,6 +99,7 @@
     key_param_ = base::StringPrintf(
         "&key=%s", base::EscapeQueryParamValue(key, true).c_str());
   }
+  persister_root_path_ = base::CreateUniqueTempDirectoryScopedToTest();
   SetNewPingManager(std::nullopt, std::nullopt, std::nullopt);
 }
 
@@ -121,7 +126,8 @@
           base::BindRepeating([]() { return false; })),
       webui_delegate_.get(), base::SequencedTaskRunner::GetCurrentDefault(),
       get_user_population_callback.value_or(base::NullCallback()),
-      get_page_load_token_callback.value_or(base::NullCallback()), nullptr));
+      get_page_load_token_callback.value_or(base::NullCallback()), nullptr,
+      persister_root_path_));
 }
 
 void PingManagerTest::SetUpFeatureList(bool should_enable_remove_cookies) {
@@ -566,6 +572,53 @@
       /*expect_cookies_removed=*/false);
 }
 
+TEST_F(PingManagerTest, PersistThreatDetails) {
+  std::unique_ptr<ClientSafeBrowsingReportRequest> report =
+      std::make_unique<ClientSafeBrowsingReportRequest>();
+  report->set_type(
+      ClientSafeBrowsingReportRequest::DANGEROUS_DOWNLOAD_PROFILE_CLOSED);
+  report->set_url("https://some.url.com/");
+  PingManager::PersistThreatDetailsResult result =
+      ping_manager()->PersistThreatDetailsAndReportOnNextStartup(
+          std::move(report));
+  EXPECT_EQ(result,
+            PingManager::PersistThreatDetailsResult::kPersistTaskPosted);
+  task_environment_.RunUntilIdle();
+
+  base::FilePath persister_dir =
+      persister_root_path_.AppendASCII("DownloadReports");
+  ASSERT_TRUE(base::PathExists(persister_dir));
+  base::FileEnumerator directory_enumerator(persister_dir,
+                                            /*recursive=*/false,
+                                            base::FileEnumerator::FILES);
+  int number_of_files = 0;
+  for (base::FilePath file_name = directory_enumerator.Next();
+       !file_name.empty(); file_name = directory_enumerator.Next()) {
+    number_of_files++;
+    std::string persisted_report_string;
+    bool success = base::ReadFileToString(file_name, &persisted_report_string);
+    ASSERT_TRUE(success);
+    auto persisted_report = std::make_unique<ClientSafeBrowsingReportRequest>();
+    bool parse_successful =
+        persisted_report->ParseFromString(persisted_report_string);
+    ASSERT_TRUE(parse_successful);
+    EXPECT_EQ(
+        persisted_report->type(),
+        ClientSafeBrowsingReportRequest::DANGEROUS_DOWNLOAD_PROFILE_CLOSED);
+    EXPECT_EQ(persisted_report->url(), "https://some.url.com/");
+  }
+  EXPECT_EQ(number_of_files, 1);
+}
+
+TEST_F(PingManagerTest, PersistThreatDetailsAtShutdown_EmptyReport) {
+  std::unique_ptr<ClientSafeBrowsingReportRequest> report =
+      std::make_unique<ClientSafeBrowsingReportRequest>();
+  PingManager::PersistThreatDetailsResult result =
+      ping_manager()->PersistThreatDetailsAndReportOnNextStartup(
+          std::move(report));
+  EXPECT_EQ(result, PingManager::PersistThreatDetailsResult::kEmptyReport);
+}
+
 TEST_F(PingManagerTest, ReportSafeBrowsingHit) {
   std::unique_ptr<HitReport> hit_report = std::make_unique<HitReport>();
   std::string post_data = "testing_hit_report_post_data";
diff --git a/components/saved_tab_groups/BUILD.gn b/components/saved_tab_groups/BUILD.gn
index ca24948..562a177 100644
--- a/components/saved_tab_groups/BUILD.gn
+++ b/components/saved_tab_groups/BUILD.gn
@@ -39,6 +39,10 @@
 
   if (is_android) {
     sources += [
+      "android/tab_group_sync_conversions_bridge.cc",
+      "android/tab_group_sync_conversions_bridge.h",
+      "android/tab_group_sync_conversions_utils.cc",
+      "android/tab_group_sync_conversions_utils.h",
       "android/tab_group_sync_service_android.cc",
       "android/tab_group_sync_service_android.h",
     ]
@@ -54,6 +58,7 @@
     sources = [
       "android/java/src/org/chromium/components/tab_group_sync/SavedTabGroup.java",
       "android/java/src/org/chromium/components/tab_group_sync/SavedTabGroupTab.java",
+      "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncConversionsBridge.java",
       "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java",
       "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java",
     ]
@@ -79,7 +84,10 @@
       "//chrome/browser",
     ]
 
-    sources = [ "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java" ]
+    sources = [
+      "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncConversionsBridge.java",
+      "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java",
+    ]
   }
 
   java_cpp_enum("tab_group_sync_enums_java") {
@@ -94,6 +102,8 @@
     "saved_tab_group_model_unittest.cc",
     "saved_tab_group_proto_conversion_unittest.cc",
     "saved_tab_group_sync_bridge_unittest.cc",
+    "saved_tab_group_test_utils.cc",
+    "saved_tab_group_test_utils.h",
     "saved_tab_group_unittest.cc",
     "tab_group_sync_service_unittest.cc",
   ]
diff --git a/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncConversionsBridge.java b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncConversionsBridge.java
new file mode 100644
index 0000000..55d97ad8
--- /dev/null
+++ b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncConversionsBridge.java
@@ -0,0 +1,63 @@
+// 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.components.tab_group_sync;
+
+import org.jni_zero.CalledByNative;
+import org.jni_zero.JNINamespace;
+
+import org.chromium.url.GURL;
+
+/**
+ * Java counterpart to the C++ TabGroupSyncConversionsBridge class. This class has no public members
+ * or methods and is meant as a private factory to build {@link SavedTabGroup} instances.
+ */
+@JNINamespace("tab_groups")
+public class TabGroupSyncConversionsBridge {
+
+    @CalledByNative
+    private static SavedTabGroup createGroup(
+            String syncId,
+            int localId,
+            String title,
+            int color,
+            long creationTimeMs,
+            long updateTimeMs) {
+        SavedTabGroup group = new SavedTabGroup();
+        group.syncId = syncId;
+        group.localId = localId == -1 ? null : localId;
+        group.title = title;
+        group.color = color;
+        assert group.color != -1;
+        group.creationTimeMs = creationTimeMs;
+        group.updateTimeMs = updateTimeMs;
+        return group;
+    }
+
+    @CalledByNative
+    private static SavedTabGroupTab createTabAndMaybeAddToGroup(
+            String syncId,
+            int localId,
+            String syncGroupId,
+            int position,
+            GURL url,
+            String title,
+            long creationTimeMs,
+            long updateTimeMs,
+            SavedTabGroup group) {
+        SavedTabGroupTab tab = new SavedTabGroupTab();
+        tab.syncId = syncId;
+        tab.localId = localId == -1 ? null : localId;
+        tab.syncGroupId = syncGroupId;
+        tab.position = position == -1 ? null : position;
+        tab.url = url;
+        tab.title = title;
+        tab.creationTimeMs = creationTimeMs;
+        tab.updateTimeMs = updateTimeMs;
+        if (group != null) {
+            group.savedTabs.add(tab);
+        }
+        return tab;
+    }
+}
diff --git a/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java
index 90741939..e725880a 100644
--- a/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java
+++ b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java
@@ -4,6 +4,11 @@
 
 package org.chromium.components.tab_group_sync;
 
+import androidx.annotation.NonNull;
+
+import org.chromium.components.tab_groups.TabGroupColorId;
+import org.chromium.url.GURL;
+
 /**
  * The core service class for handling tab group sync across devices. Provides 1. Mutation methods
  * to propagate local changes to remote. 2. Observer interface to propagate remote changes to local.
@@ -15,17 +20,30 @@
      */
     interface Observer {
         /**
-         * A new tab group was added, or an existing tab group was updated at the given source.
+         * Called when a new tab group was added from sync. A corresponding local tab group should
+         * be created.
          *
-         * @param group The {@link SavedTabGroup} that was added or updated.
-         * @param source The source of the change (local or remote).
+         * @param group The {@link SavedTabGroup} that was added from sync.
          */
-        void onTabGroupAddedOrUpdated(SavedTabGroup group, @TriggerSource int source);
+        void onTabGroupAdded(SavedTabGroup group);
+
+        /**
+         * Called when a tab group is updated from sync. The corresponding local tab group should be
+         * updated to match the sync representation.
+         *
+         * @param group The {@link SavedTabGroup} that was updated from sync.
+         */
+        void onTabGroupUpdated(SavedTabGroup group);
+
+        /**
+         * Called when the sync database (ModelTypeStore) has been initialized and fully loaded to
+         * memory.
+         */
+        void onInitialized();
 
         /**
          * Called when a tab group is deleted from sync. The local tab group should be deleted in
-         * response and all the corresponding tabs should be closed. TODO(b/331466817): Build
-         * mechanism to distinguish between tab group deletion and ungroup events correctly.
+         * response and all the corresponding tabs should be closed.
          *
          * @param localId The local ID corresponding to the tab group that was removed.
          */
@@ -46,10 +64,95 @@
      */
     void removeObserver(Observer observer);
 
+    /** Creates a remote tab group with the given local group ID. */
+    void createGroup(int groupId);
+
     /**
-     * Removes a group from sync in response to a local group removal.
+     * Removes a remote tab group in response to a local group removal.
      *
      * @param groupId The ID of the tab group removed. Currently this is the root ID of the group.
      */
     void removeGroup(int groupId);
+
+    /**
+     * Updates the visual info of the remote tab group.
+     *
+     * @param tabGroupId The local group ID of the corresponding tab group.
+     * @param title The title of the tab group.
+     * @param color The color of the tab group.
+     */
+    void updateVisualData(int tabGroupId, @NonNull String title, @TabGroupColorId int color);
+
+    /**
+     * Adds a tab to a remote group. Should be called with response to a local tab addition to a tab
+     * group. If position is -1, adds the tab to the end of the group.
+     *
+     * @param tabGroupId The local ID of the corresponding tab group.
+     * @param tabId The local tab ID of the tab being added.
+     * @param title The title of the tab.
+     * @param url The URL of the tab.
+     * @param position The position in the remote tab group at which the tab should be inserted. If
+     *     -1, inserts it at the end of the group.
+     */
+    void addTab(int tabGroupId, int tabId, String title, GURL url, int position);
+
+    /**
+     * Updates a remote tab corresponding to local tab ID {@param tabId}.
+     *
+     * @param tabGroupId The local ID of the corresponding tab group.
+     * @param tabId The local tab ID of the tab being added.
+     * @param title The title of the tab.
+     * @param url The URL of the tab.
+     * @param position The position in the remote tab group at which the tab should be inserted. If
+     *     -1, inserts it at the end of the group.
+     */
+    void updateTab(int tabGroupId, int tabId, String title, GURL url, int position);
+
+    /**
+     * Removes a tab from the remote tab group.
+     *
+     * @param tabGroupId The local group ID of the corresponding tab group.
+     * @param tabId The local tab ID of the tab being removed.
+     */
+    void removeTab(int tabGroupId, int tabId);
+
+    /**
+     * Called to return all the remote tab group IDs currently existing in the system.
+     *
+     * @return An array of IDs of the currently known tab groups.
+     */
+    String[] getAllGroupIds();
+
+    /**
+     * Returns a single {@link SavedTabGroup}.
+     *
+     * @param syncGroupId The sync ID of the group to be returned.
+     * @return The associated {@link SavedTabGroup}.
+     */
+    SavedTabGroup getGroup(String syncGroupId);
+
+    /**
+     * Returns a single {@link SavedTabGroup}.
+     *
+     * @param localGroupId The local ID of the group to be returned.
+     * @return The associated {@link SavedTabGroup}.
+     */
+    SavedTabGroup getGroup(int localGroupId);
+
+    /**
+     * Updates the in-memory mapping between sync and local tab group IDs.
+     *
+     * @param syncId The remote tab group ID.
+     * @param localId The local tab group ID.
+     */
+    void updateLocalTabGroupId(String syncId, int localId);
+
+    /**
+     * Updates the in-memory mapping between sync and local IDs for a given tab.
+     *
+     * @param localGroupId The local group ID of the corresponding tab group.
+     * @param syncTabId The sync ID of the corresponding tab.
+     * @param localTabId The local ID of the corresponding tab.
+     */
+    void updateLocalTabId(int localGroupId, String syncTabId, int localTabId);
 }
diff --git a/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java
index 93afeb6..e32d7fec 100644
--- a/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java
+++ b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java
@@ -9,6 +9,9 @@
 import org.jni_zero.NativeMethods;
 
 import org.chromium.base.ObserverList;
+import org.chromium.base.task.PostTask;
+import org.chromium.base.task.TaskTraits;
+import org.chromium.url.GURL;
 
 /**
  * Java side of the JNI bridge between TabGroupSyncServiceImpl in Java and C++. All method calls are
@@ -18,6 +21,7 @@
 public class TabGroupSyncServiceImpl implements TabGroupSyncService {
     private final ObserverList<TabGroupSyncService.Observer> mObservers = new ObserverList<>();
     private long mNativePtr;
+    private boolean mInitialized;
 
     @CalledByNative
     private static TabGroupSyncServiceImpl create(long nativePtr) {
@@ -29,14 +33,13 @@
     }
 
     @Override
-    public void removeGroup(int groupId) {
-        if (mNativePtr == 0) return;
-        TabGroupSyncServiceImplJni.get().removeGroup(mNativePtr, this, groupId);
-    }
-
-    @Override
     public void addObserver(TabGroupSyncService.Observer observer) {
         mObservers.addObserver(observer);
+
+        // If initialization is already complete, notify the newly added observer.
+        if (mInitialized) {
+            PostTask.postTask(TaskTraits.UI_DEFAULT, () -> observer.onInitialized());
+        }
     }
 
     @Override
@@ -44,14 +47,175 @@
         mObservers.removeObserver(observer);
     }
 
+    @Override
+    public void createGroup(int groupId) {
+        if (mNativePtr == 0) return;
+        TabGroupSyncServiceImplJni.get().createGroup(mNativePtr, this, groupId);
+    }
+
+    @Override
+    public void removeGroup(int groupId) {
+        if (mNativePtr == 0) return;
+        TabGroupSyncServiceImplJni.get().removeGroup(mNativePtr, this, groupId);
+    }
+
+    @Override
+    public void updateVisualData(int tabGroupId, String title, int color) {
+        if (mNativePtr == 0) return;
+        TabGroupSyncServiceImplJni.get()
+                .updateVisualData(mNativePtr, this, tabGroupId, title, color);
+    }
+
+    @Override
+    public void addTab(int groupId, int tabId, String title, GURL url, int position) {
+        if (mNativePtr == 0) return;
+        TabGroupSyncServiceImplJni.get()
+                .addTab(mNativePtr, this, groupId, tabId, title, url, position);
+    }
+
+    @Override
+    public void updateTab(int groupId, int tabId, String title, GURL url, int position) {
+        if (mNativePtr == 0) return;
+        TabGroupSyncServiceImplJni.get()
+                .updateTab(mNativePtr, this, groupId, tabId, title, url, position);
+    }
+
+    @Override
+    public void removeTab(int groupId, int tabId) {
+        if (mNativePtr == 0) return;
+        TabGroupSyncServiceImplJni.get().removeTab(mNativePtr, this, groupId, tabId);
+    }
+
+    @Override
+    public String[] getAllGroupIds() {
+        if (mNativePtr == 0) return null;
+        return TabGroupSyncServiceImplJni.get().getAllGroupIds(mNativePtr, this);
+    }
+
+    @Override
+    public SavedTabGroup getGroup(String syncGroupId) {
+        if (mNativePtr == 0) return null;
+        return TabGroupSyncServiceImplJni.get()
+                .getGroupBySyncGroupId(mNativePtr, this, syncGroupId);
+    }
+
+    @Override
+    public SavedTabGroup getGroup(int localGroupId) {
+        if (mNativePtr == 0) return null;
+        return TabGroupSyncServiceImplJni.get()
+                .getGroupByLocalGroupId(mNativePtr, this, localGroupId);
+    }
+
+    @Override
+    public void updateLocalTabGroupId(String syncId, int localId) {
+        if (mNativePtr == 0) return;
+        TabGroupSyncServiceImplJni.get().updateLocalTabGroupId(mNativePtr, this, syncId, localId);
+    }
+
+    @Override
+    public void updateLocalTabId(int localGroupId, String syncTabId, int localTabId) {
+        if (mNativePtr == 0) return;
+        TabGroupSyncServiceImplJni.get()
+                .updateLocalTabId(mNativePtr, this, localGroupId, syncTabId, localTabId);
+    }
+
     @CalledByNative
     private void clearNativePtr() {
         mNativePtr = 0;
     }
 
+    @CalledByNative
+    private void onInitialized() {
+        mInitialized = true;
+        for (Observer observer : mObservers) {
+            observer.onInitialized();
+        }
+    }
+
+    @CalledByNative
+    private void onTabGroupAdded(SavedTabGroup group) {
+        for (Observer observer : mObservers) {
+            observer.onTabGroupAdded(group);
+        }
+    }
+
+    @CalledByNative
+    private void onTabGroupUpdated(SavedTabGroup group) {
+        for (Observer observer : mObservers) {
+            observer.onTabGroupUpdated(group);
+        }
+    }
+
+    @CalledByNative
+    private void onTabGroupRemoved(int localId) {
+        for (Observer observer : mObservers) {
+            observer.onTabGroupRemoved(localId);
+        }
+    }
+
     @NativeMethods
     interface Natives {
+        void createGroup(
+                long nativeTabGroupSyncServiceAndroid, TabGroupSyncServiceImpl caller, int groupId);
+
         void removeGroup(
                 long nativeTabGroupSyncServiceAndroid, TabGroupSyncServiceImpl caller, int groupId);
+
+        void updateVisualData(
+                long nativeTabGroupSyncServiceAndroid,
+                TabGroupSyncServiceImpl caller,
+                int tabGroupId,
+                String title,
+                int color);
+
+        void addTab(
+                long nativeTabGroupSyncServiceAndroid,
+                TabGroupSyncServiceImpl caller,
+                int groupId,
+                int tabId,
+                String title,
+                GURL url,
+                int position);
+
+        void updateTab(
+                long nativeTabGroupSyncServiceAndroid,
+                TabGroupSyncServiceImpl caller,
+                int groupId,
+                int tabId,
+                String title,
+                GURL url,
+                int position);
+
+        void removeTab(
+                long nativeTabGroupSyncServiceAndroid,
+                TabGroupSyncServiceImpl caller,
+                int groupId,
+                int tabId);
+
+        String[] getAllGroupIds(
+                long nativeTabGroupSyncServiceAndroid, TabGroupSyncServiceImpl caller);
+
+        SavedTabGroup getGroupBySyncGroupId(
+                long nativeTabGroupSyncServiceAndroid,
+                TabGroupSyncServiceImpl caller,
+                String syncGroupId);
+
+        SavedTabGroup getGroupByLocalGroupId(
+                long nativeTabGroupSyncServiceAndroid,
+                TabGroupSyncServiceImpl caller,
+                int localGroupId);
+
+        void updateLocalTabGroupId(
+                long nativeTabGroupSyncServiceAndroid,
+                TabGroupSyncServiceImpl caller,
+                String syncId,
+                int localId);
+
+        void updateLocalTabId(
+                long nativeTabGroupSyncServiceAndroid,
+                TabGroupSyncServiceImpl caller,
+                int localGroupId,
+                String syncTabId,
+                int localTabId);
     }
 }
diff --git a/components/saved_tab_groups/android/tab_group_sync_conversions_bridge.cc b/components/saved_tab_groups/android/tab_group_sync_conversions_bridge.cc
new file mode 100644
index 0000000..e00cc88
--- /dev/null
+++ b/components/saved_tab_groups/android/tab_group_sync_conversions_bridge.cc
@@ -0,0 +1,76 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/saved_tab_groups/android/tab_group_sync_conversions_bridge.h"
+
+#include <optional>
+#include <vector>
+
+#include "base/android/jni_string.h"
+#include "base/uuid.h"
+#include "components/saved_tab_groups/android/tab_group_sync_conversions_utils.h"
+#include "components/saved_tab_groups/jni_headers/TabGroupSyncConversionsBridge_jni.h"
+#include "components/saved_tab_groups/saved_tab_group.h"
+#include "components/saved_tab_groups/saved_tab_group_tab.h"
+#include "url/android/gurl_android.h"
+
+using base::android::ConvertUTF16ToJavaString;
+using base::android::JavaParamRef;
+using base::android::ScopedJavaLocalRef;
+
+namespace tab_groups {
+namespace {
+
+// Unspecified tab position is represented as -1 in Java.
+int kInvalidTabPosition = -1;
+
+// Helper method to create a Java SavedTabGroupTab, and optionally add it to
+// a group if a non-null |j_tab_group| is specified.
+ScopedJavaLocalRef<jobject>
+JNI_TabGroupSyncConversionsBridge_createTabAndMaybeAddToGroup(
+    JNIEnv* env,
+    const SavedTabGroupTab& tab,
+    ScopedJavaLocalRef<jobject>& j_tab_group) {
+  auto j_sync_tab_id = UuidToJavaString(env, tab.saved_tab_guid());
+  auto j_local_tab_id = ToJavaTabId(tab.local_tab_id());
+  auto j_sync_group_id = UuidToJavaString(env, tab.saved_group_guid());
+  auto j_url = url::GURLAndroid::FromNativeGURL(env, tab.url());
+  return Java_TabGroupSyncConversionsBridge_createTabAndMaybeAddToGroup(
+      env, j_sync_tab_id, j_local_tab_id, j_sync_group_id,
+      static_cast<int32_t>(tab.position().value_or(kInvalidTabPosition)), j_url,
+      ConvertUTF16ToJavaString(env, tab.title()),
+      tab.creation_time_windows_epoch_micros().InMillisecondsSinceUnixEpoch(),
+      tab.update_time_windows_epoch_micros().InMillisecondsSinceUnixEpoch(),
+      j_tab_group);
+}
+
+// Helper method to create a Java SavedTabGroup. This doesn't include the tabs.
+ScopedJavaLocalRef<jobject> JNI_TabGroupSyncConversionsBridge_createGroup(
+    JNIEnv* env,
+    const SavedTabGroup& group) {
+  auto j_sync_id = UuidToJavaString(env, group.saved_guid());
+  auto j_local_id = ToJavaTabGroupId(group.local_group_id());
+  return Java_TabGroupSyncConversionsBridge_createGroup(
+      env, j_sync_id, j_local_id, ConvertUTF16ToJavaString(env, group.title()),
+      static_cast<int32_t>(group.color()),
+      group.creation_time_windows_epoch_micros().InMillisecondsSinceUnixEpoch(),
+      group.update_time_windows_epoch_micros().InMillisecondsSinceUnixEpoch());
+}
+
+}  // namespace
+
+// static
+ScopedJavaLocalRef<jobject> TabGroupSyncConversionsBridge::CreateGroup(
+    JNIEnv* env,
+    const SavedTabGroup& group) {
+  auto j_tab_group = JNI_TabGroupSyncConversionsBridge_createGroup(env, group);
+  for (const auto& tab : group.saved_tabs()) {
+    JNI_TabGroupSyncConversionsBridge_createTabAndMaybeAddToGroup(env, tab,
+                                                                  j_tab_group);
+  }
+
+  return j_tab_group;
+}
+
+}  // namespace tab_groups
diff --git a/components/saved_tab_groups/android/tab_group_sync_conversions_bridge.h b/components/saved_tab_groups/android/tab_group_sync_conversions_bridge.h
new file mode 100644
index 0000000..619cad2
--- /dev/null
+++ b/components/saved_tab_groups/android/tab_group_sync_conversions_bridge.h
@@ -0,0 +1,31 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SAVED_TAB_GROUPS_ANDROID_TAB_GROUP_SYNC_CONVERSIONS_BRIDGE_H_
+#define COMPONENTS_SAVED_TAB_GROUPS_ANDROID_TAB_GROUP_SYNC_CONVERSIONS_BRIDGE_H_
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "components/saved_tab_groups/saved_tab_group.h"
+
+using base::android::ScopedJavaLocalRef;
+
+namespace tab_groups {
+
+// A helper class for creating Java SavedTabGroup instances from its native
+// counterpart.
+class TabGroupSyncConversionsBridge {
+ public:
+  // Creates a Java SavedTabGroup from the given |group|.
+  static base::android::ScopedJavaLocalRef<jobject> CreateGroup(
+      JNIEnv* env,
+      const SavedTabGroup& group);
+
+ private:
+  TabGroupSyncConversionsBridge() = default;
+};
+
+}  // namespace tab_groups
+
+#endif  // COMPONENTS_SAVED_TAB_GROUPS_ANDROID_TAB_GROUP_SYNC_CONVERSIONS_BRIDGE_H_
diff --git a/components/saved_tab_groups/android/tab_group_sync_conversions_utils.cc b/components/saved_tab_groups/android/tab_group_sync_conversions_utils.cc
new file mode 100644
index 0000000..1b02f1de
--- /dev/null
+++ b/components/saved_tab_groups/android/tab_group_sync_conversions_utils.cc
@@ -0,0 +1,54 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/saved_tab_groups/android/tab_group_sync_conversions_utils.h"
+
+#include <optional>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/uuid.h"
+#include "components/saved_tab_groups/types.h"
+
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::JavaParamRef;
+using base::android::ScopedJavaLocalRef;
+
+namespace tab_groups {
+namespace {
+
+// Invalid IDs are represented as -1 in the JNI bridge.
+int kInvalidTabGroupId = -1;
+int kInvalidTabId = -1;
+
+}  // namespace
+
+LocalTabGroupID FromJavaTabGroupId(int group_id) {
+  return group_id;
+}
+
+jint ToJavaTabGroupId(const std::optional<LocalTabGroupID>& group_id) {
+  return group_id.value_or(kInvalidTabGroupId);
+}
+
+LocalTabID FromJavaTabId(int tab_id) {
+  return tab_id;
+}
+
+jint ToJavaTabId(const std::optional<LocalTabID>& tab_id) {
+  return tab_id.value_or(kInvalidTabId);
+}
+
+ScopedJavaLocalRef<jstring> UuidToJavaString(JNIEnv* env,
+                                             const base::Uuid& uuid) {
+  return ConvertUTF8ToJavaString(env, uuid.AsLowercaseString());
+}
+
+base::Uuid JavaStringToUuid(JNIEnv* env, const JavaParamRef<jstring>& j_uuid) {
+  return base::Uuid::ParseLowercase(ConvertJavaStringToUTF8(env, j_uuid));
+}
+
+}  // namespace tab_groups
diff --git a/components/saved_tab_groups/android/tab_group_sync_conversions_utils.h b/components/saved_tab_groups/android/tab_group_sync_conversions_utils.h
new file mode 100644
index 0000000..a088eafe
--- /dev/null
+++ b/components/saved_tab_groups/android/tab_group_sync_conversions_utils.h
@@ -0,0 +1,44 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SAVED_TAB_GROUPS_ANDROID_TAB_GROUP_SYNC_CONVERSIONS_UTILS_H_
+#define COMPONENTS_SAVED_TAB_GROUPS_ANDROID_TAB_GROUP_SYNC_CONVERSIONS_UTILS_H_
+
+#include <optional>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/uuid.h"
+#include "components/saved_tab_groups/types.h"
+
+using base::android::JavaParamRef;
+using base::android::ScopedJavaLocalRef;
+
+namespace tab_groups {
+
+// Converts a Java local tab group ID (root ID) to its
+// native representation.
+LocalTabGroupID FromJavaTabGroupId(int group_id);
+
+// Converts a local tab group ID in native to Java. If the tab group ID isn't
+// present, -1 will be returned.
+jint ToJavaTabGroupId(const std::optional<LocalTabGroupID>& group_id);
+
+// Converts a Java local tab ID to its native representation.
+LocalTabID FromJavaTabId(int tab_id);
+
+// Converts a local tab ID in native to its Java counterpart. If tab ID isn't
+// present, -1 will be returned.
+jint ToJavaTabId(const std::optional<LocalTabID>& tab_id);
+
+// Converts a base::Uuid to a Java string.
+ScopedJavaLocalRef<jstring> UuidToJavaString(JNIEnv* env,
+                                             const base::Uuid& uuid);
+
+// Converts a Java string to base::Uuid.
+base::Uuid JavaStringToUuid(JNIEnv* env, const JavaParamRef<jstring>& j_uuid);
+
+}  // namespace tab_groups
+
+#endif  // COMPONENTS_SAVED_TAB_GROUPS_ANDROID_TAB_GROUP_SYNC_CONVERSIONS_UTILS_H_
diff --git a/components/saved_tab_groups/android/tab_group_sync_service_android.cc b/components/saved_tab_groups/android/tab_group_sync_service_android.cc
index 5028d1c6..41a95a7d 100644
--- a/components/saved_tab_groups/android/tab_group_sync_service_android.cc
+++ b/components/saved_tab_groups/android/tab_group_sync_service_android.cc
@@ -6,17 +6,19 @@
 
 #include <memory>
 
+#include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/saved_tab_groups/android/tab_group_sync_conversions_bridge.h"
+#include "components/saved_tab_groups/android/tab_group_sync_conversions_utils.h"
 #include "components/saved_tab_groups/jni_headers/TabGroupSyncServiceImpl_jni.h"
 #include "components/saved_tab_groups/tab_group_sync_service.h"
+#include "url/android/gurl_android.h"
 
 using base::android::AttachCurrentThread;
 using base::android::ConvertJavaStringToUTF16;
-using base::android::ConvertJavaStringToUTF8;
-using base::android::ConvertUTF8ToJavaString;
 using base::android::JavaParamRef;
-using base::android::ScopedJavaGlobalRef;
 using base::android::ScopedJavaLocalRef;
 
 namespace tab_groups {
@@ -27,8 +29,7 @@
 }  // namespace
 
 // This function is declared in tab_group_sync_service.h and
-// should be linked in to any binary using
-// TabGroupSyncService::GetJavaObject.
+// should be linked in to any binary using TabGroupSyncService::GetJavaObject.
 // static
 ScopedJavaLocalRef<jobject> TabGroupSyncService::GetJavaObject(
     TabGroupSyncService* service) {
@@ -64,22 +65,169 @@
   return ScopedJavaLocalRef<jobject>(java_obj_);
 }
 
+void TabGroupSyncServiceAndroid::OnInitialized() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_TabGroupSyncServiceImpl_onInitialized(env, java_obj_);
+}
+
+void TabGroupSyncServiceAndroid::OnTabGroupAdded(const SavedTabGroup& group,
+                                                 TriggerSource source) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  auto j_group = TabGroupSyncConversionsBridge::CreateGroup(env, group);
+  Java_TabGroupSyncServiceImpl_onTabGroupAdded(env, java_obj_, j_group);
+}
+
+void TabGroupSyncServiceAndroid::OnTabGroupUpdated(const SavedTabGroup& group,
+                                                   TriggerSource source) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  auto j_group = TabGroupSyncConversionsBridge::CreateGroup(env, group);
+  Java_TabGroupSyncServiceImpl_onTabGroupUpdated(env, java_obj_, j_group);
+}
+
+void TabGroupSyncServiceAndroid::OnTabGroupRemoved(
+    const LocalTabGroupID& local_id) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_TabGroupSyncServiceImpl_onTabGroupRemoved(env, java_obj_,
+                                                 ToJavaTabGroupId(local_id));
+}
+
+ScopedJavaLocalRef<jstring> TabGroupSyncServiceAndroid::CreateGroup(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_caller,
+    jint j_group_id) {
+  auto group_id = FromJavaTabGroupId(j_group_id);
+
+  SavedTabGroup group(std::u16string(), tab_groups::TabGroupColorId::kGrey,
+                      std::vector<SavedTabGroupTab>(), std::nullopt,
+                      std::nullopt, group_id);
+  return UuidToJavaString(env, group.saved_guid());
+}
+
 void TabGroupSyncServiceAndroid::RemoveGroup(
     JNIEnv* env,
     const JavaParamRef<jobject>& j_caller,
     jint j_group_id) {
-  // TODO(b/329124957): Implement.
+  auto group_id = FromJavaTabGroupId(j_group_id);
+  tab_group_sync_service_->RemoveGroup(group_id);
 }
 
-void TabGroupSyncServiceAndroid::OnTabGroupAddedOrUpdated(
-    const SavedTabGroup& group,
-    TriggerSource source) {
-  // TODO(b/329124957): Implement.
+void TabGroupSyncServiceAndroid::UpdateVisualData(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_caller,
+    jint j_group_id,
+    const JavaParamRef<jstring>& j_title,
+    jint j_color) {
+  auto group_id = FromJavaTabGroupId(j_group_id);
+  auto title = ConvertJavaStringToUTF16(env, j_title);
+  auto color = static_cast<tab_groups::TabGroupColorId>(j_color);
+  TabGroupVisualData visual_data(title, color, /*is_collapsed=*/false);
+  tab_group_sync_service_->UpdateVisualData(group_id, &visual_data);
 }
 
-void TabGroupSyncServiceAndroid::OnTabGroupRemoved(
-    const tab_groups::TabGroupId& local_id) {
-  // TODO(b/329124957): Implement.
+void TabGroupSyncServiceAndroid::AddTab(JNIEnv* env,
+                                        const JavaParamRef<jobject>& j_caller,
+                                        jint j_group_id,
+                                        jint j_tab_id,
+                                        const JavaParamRef<jstring>& j_title,
+                                        const JavaParamRef<jobject>& j_url,
+                                        jint j_position) {
+  auto group_id = FromJavaTabGroupId(j_group_id);
+  auto tab_id = FromJavaTabId(j_tab_id);
+  auto title = ConvertJavaStringToUTF16(env, j_title);
+  auto url = url::GURLAndroid::ToNativeGURL(env, j_url);
+  std::optional<size_t> position =
+      j_position < 0 ? std::nullopt : std::make_optional<size_t>(j_position);
+  tab_group_sync_service_->AddTab(group_id, tab_id, title, *url, position);
+}
+
+void TabGroupSyncServiceAndroid::UpdateTab(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_caller,
+    jint j_group_id,
+    jint j_tab_id,
+    const JavaParamRef<jstring>& j_title,
+    const JavaParamRef<jobject>& j_url,
+    jint j_position) {
+  auto group_id = FromJavaTabGroupId(j_group_id);
+  auto tab_id = FromJavaTabId(j_tab_id);
+  auto title = ConvertJavaStringToUTF16(env, j_title);
+  auto url = url::GURLAndroid::ToNativeGURL(env, j_url);
+  std::optional<size_t> position =
+      j_position < 0 ? std::nullopt : std::make_optional<size_t>(j_position);
+  tab_group_sync_service_->UpdateTab(group_id, tab_id, title, *url, position);
+}
+
+void TabGroupSyncServiceAndroid::RemoveTab(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_caller,
+    jint j_group_id,
+    jint j_tab_id) {
+  auto group_id = FromJavaTabGroupId(j_group_id);
+  auto tab_id = FromJavaTabId(j_tab_id);
+  tab_group_sync_service_->RemoveTab(group_id, tab_id);
+}
+
+ScopedJavaLocalRef<jobjectArray> TabGroupSyncServiceAndroid::GetAllGroupIds(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_caller) {
+  std::vector<std::string> sync_ids;
+  const auto& groups = tab_group_sync_service_->GetAllGroups();
+  for (const auto& group : groups) {
+    sync_ids.emplace_back(group.saved_guid().AsLowercaseString());
+  }
+
+  return base::android::ToJavaArrayOfStrings(env, sync_ids);
+}
+
+ScopedJavaLocalRef<jobject> TabGroupSyncServiceAndroid::GetGroupBySyncGroupId(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_caller,
+    const JavaParamRef<jstring>& j_sync_group_id) {
+  auto sync_group_id = JavaStringToUuid(env, j_sync_group_id);
+
+  std::optional<SavedTabGroup> group =
+      tab_group_sync_service_->GetGroup(sync_group_id);
+  if (!group.has_value()) {
+    return ScopedJavaLocalRef<jobject>();
+  }
+
+  return TabGroupSyncConversionsBridge::CreateGroup(env, group.value());
+}
+
+ScopedJavaLocalRef<jobject> TabGroupSyncServiceAndroid::GetGroupByLocalGroupId(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_caller,
+    jint j_local_group_id) {
+  auto local_group_id = FromJavaTabGroupId(j_local_group_id);
+  std::optional<SavedTabGroup> group =
+      tab_group_sync_service_->GetGroup(local_group_id);
+  if (!group.has_value()) {
+    return ScopedJavaLocalRef<jobject>();
+  }
+  return TabGroupSyncConversionsBridge::CreateGroup(env, group.value());
+}
+
+void TabGroupSyncServiceAndroid::UpdateLocalTabGroupId(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_caller,
+    const JavaParamRef<jstring>& j_sync_id,
+    jint j_local_id) {
+  auto sync_id = JavaStringToUuid(env, j_sync_id);
+  auto local_id = FromJavaTabGroupId(j_local_id);
+  tab_group_sync_service_->UpdateLocalTabGroupId(sync_id, local_id);
+}
+
+void TabGroupSyncServiceAndroid::UpdateLocalTabId(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_caller,
+    jint j_group_id,
+    const JavaParamRef<jstring>& j_sync_tab_id,
+    jint j_local_tab_id) {
+  auto local_group_id = FromJavaTabGroupId(j_group_id);
+  auto sync_tab_id = JavaStringToUuid(env, j_sync_tab_id);
+  auto local_tab_id = FromJavaTabId(j_local_tab_id);
+  tab_group_sync_service_->UpdateLocalTabId(local_group_id, sync_tab_id,
+                                            local_tab_id);
 }
 
 }  // namespace tab_groups
diff --git a/components/saved_tab_groups/android/tab_group_sync_service_android.h b/components/saved_tab_groups/android/tab_group_sync_service_android.h
index d482fdd..102d789 100644
--- a/components/saved_tab_groups/android/tab_group_sync_service_android.h
+++ b/components/saved_tab_groups/android/tab_group_sync_service_android.h
@@ -27,23 +27,86 @@
 
   ScopedJavaLocalRef<jobject> GetJavaObject();
 
+  // TabGroupSyncService::Observer overrides.
+  void OnInitialized() override;
+  void OnTabGroupAdded(const SavedTabGroup& group,
+                       TriggerSource source) override;
+  void OnTabGroupUpdated(const SavedTabGroup& group,
+                         TriggerSource source) override;
+  void OnTabGroupRemoved(const LocalTabGroupID& local_id) override;
+
+  // Mutation methods (Java -> native).
+  // Mutator methods that result in group metadata mutation.
+  ScopedJavaLocalRef<jstring> CreateGroup(JNIEnv* env,
+                                          const JavaParamRef<jobject>& j_caller,
+                                          jint j_group_id);
+
   void RemoveGroup(JNIEnv* env,
                    const JavaParamRef<jobject>& j_caller,
                    jint j_group_id);
 
-  // TabGroupSyncService::Observer overrides.
-  void OnTabGroupAddedOrUpdated(const SavedTabGroup& group,
-                                TriggerSource source) override;
-  void OnTabGroupRemoved(const tab_groups::TabGroupId& local_id) override;
+  void UpdateVisualData(JNIEnv* env,
+                        const JavaParamRef<jobject>& j_caller,
+                        jint j_group_id,
+                        const JavaParamRef<jstring>& j_title,
+                        jint j_color);
+
+  // Mutator methods that result in tab metadata mutation.
+  void AddTab(JNIEnv* env,
+              const JavaParamRef<jobject>& j_caller,
+              jint j_group_id,
+              jint j_tab_id,
+              const JavaParamRef<jstring>& j_title,
+              const JavaParamRef<jobject>& j_url,
+              jint j_position);
+
+  void UpdateTab(JNIEnv* env,
+                 const JavaParamRef<jobject>& j_caller,
+                 jint j_group_id,
+                 jint j_tab_id,
+                 const JavaParamRef<jstring>& j_title,
+                 const JavaParamRef<jobject>& j_url,
+                 jint j_position);
+
+  void RemoveTab(JNIEnv* env,
+                 const JavaParamRef<jobject>& j_caller,
+                 jint j_group_id,
+                 jint j_tab_id);
+
+  // Accessor methods.
+  ScopedJavaLocalRef<jobjectArray> GetAllGroupIds(
+      JNIEnv* env,
+      const JavaParamRef<jobject>& j_caller);
+
+  ScopedJavaLocalRef<jobject> GetGroupBySyncGroupId(
+      JNIEnv* env,
+      const JavaParamRef<jobject>& j_caller,
+      const JavaParamRef<jstring>& j_sync_group_id);
+
+  ScopedJavaLocalRef<jobject> GetGroupByLocalGroupId(
+      JNIEnv* env,
+      const JavaParamRef<jobject>& j_caller,
+      jint j_group_id);
+
+  // Book-keeping methods to maintain in-memory mapping of sync and local IDs.
+  void UpdateLocalTabGroupId(JNIEnv* env,
+                             const JavaParamRef<jobject>& j_caller,
+                             const JavaParamRef<jstring>& j_sync_id,
+                             jint j_local_id);
+  void UpdateLocalTabId(JNIEnv* env,
+                        const JavaParamRef<jobject>& j_caller,
+                        jint j_group_id,
+                        const JavaParamRef<jstring>& j_sync_tab_id,
+                        jint j_local_tab_id);
 
  private:
   // A reference to the Java counterpart of this class.  See
   // TabGroupSyncServiceImpl.java.
   ScopedJavaGlobalRef<jobject> java_obj_;
 
-  // Not owned. This is safe because the JNI bridge is destroyed and the native
-  // pointer in Java is cleared whenever TabGroupSyncService is destroyed. Hence
-  // there will be no subsequent unsafe calls to native.
+  // Not owned. This is safe because the JNI bridge is destroyed and the
+  // native pointer in Java is cleared whenever TabGroupSyncService is
+  // destroyed. Hence there will be no subsequent unsafe calls to native.
   raw_ptr<TabGroupSyncService> tab_group_sync_service_;
 };
 
diff --git a/components/saved_tab_groups/saved_tab_group_model_unittest.cc b/components/saved_tab_groups/saved_tab_group_model_unittest.cc
index df0cca0..4b2ddc90 100644
--- a/components/saved_tab_groups/saved_tab_group_model_unittest.cc
+++ b/components/saved_tab_groups/saved_tab_group_model_unittest.cc
@@ -20,6 +20,7 @@
 #include "components/saved_tab_groups/saved_tab_group.h"
 #include "components/saved_tab_groups/saved_tab_group_model_observer.h"
 #include "components/saved_tab_groups/saved_tab_group_tab.h"
+#include "components/saved_tab_groups/saved_tab_group_test_utils.h"
 #include "components/sync/protocol/saved_tab_group_specifics.pb.h"
 #include "components/tab_groups/tab_group_color.h"
 #include "components/tab_groups/tab_group_id.h"
@@ -29,82 +30,6 @@
 #include "url/url_constants.h"
 
 namespace tab_groups {
-namespace {
-
-LocalTabGroupID GenerateRandomTabGroupID() {
-#if BUILDFLAG(IS_ANDROID)
-  return base::RandInt(0, 1000);
-#else
-  return tab_groups::TabGroupId::GenerateNew();
-#endif
-}
-
-LocalTabID GenerateRandomTabID() {
-#if BUILDFLAG(IS_ANDROID)
-  return base::RandInt(0, 1000);
-#else
-  return base::Token::CreateRandom();
-#endif
-}
-
-void CompareSavedTabGroupTabs(const std::vector<SavedTabGroupTab>& v1,
-                              const std::vector<SavedTabGroupTab>& v2) {
-  ASSERT_EQ(v1.size(), v2.size());
-  for (size_t i = 0; i < v1.size(); i++) {
-    SavedTabGroupTab tab1 = v1[i];
-    SavedTabGroupTab tab2 = v2[i];
-    EXPECT_EQ(tab1.url(), tab2.url());
-    EXPECT_EQ(tab1.title(), tab2.title());
-    EXPECT_EQ(tab1.favicon(), tab2.favicon());
-  }
-}
-
-bool CompareSavedTabGroups(const SavedTabGroup& g1, const SavedTabGroup& g2) {
-  if (g1.title() != g2.title())
-    return false;
-  if (g1.color() != g2.color())
-    return false;
-  if (g1.position() != g2.position())
-    return false;
-  if (g1.saved_guid() != g2.saved_guid())
-    return false;
-  if (g1.creation_time_windows_epoch_micros() !=
-      g2.creation_time_windows_epoch_micros()) {
-    return false;
-  }
-
-  return true;
-}
-
-SavedTabGroupTab CreateSavedTabGroupTab(const std::string& url,
-                                        const std::u16string& title,
-                                        const base::Uuid& group_guid,
-                                        std::optional<int> position) {
-  SavedTabGroupTab tab(GURL(url), title, group_guid, position);
-  tab.SetFavicon(gfx::Image());
-  return tab;
-}
-
-SavedTabGroup CreateTestSavedTabGroup() {
-  base::Uuid id = base::Uuid::GenerateRandomV4();
-  const std::u16string title = u"Test Test";
-  const tab_groups::TabGroupColorId& color = tab_groups::TabGroupColorId::kBlue;
-
-  SavedTabGroupTab tab1 =
-      CreateSavedTabGroupTab("www.google.com", u"Google", id, /*position=*/0);
-  SavedTabGroupTab tab2 =
-      CreateSavedTabGroupTab("chrome://newtab", u"new tab", id, /*position=*/1);
-
-  tab1.SetFavicon(gfx::Image());
-  tab2.SetFavicon(gfx::Image());
-
-  std::vector<SavedTabGroupTab> tabs = {tab1, tab2};
-
-  SavedTabGroup group(title, color, tabs, std::nullopt, id);
-  return group;
-}
-
-}  // namespace
 
 // Serves to test the functions in SavedTabGroupModelObserver.
 class SavedTabGroupModelObserverTest
@@ -237,18 +162,19 @@
     const tab_groups::TabGroupColorId& color_3 =
         tab_groups::TabGroupColorId::kGreen;
 
-    std::vector<SavedTabGroupTab> group_1_tabs = {
-        CreateSavedTabGroupTab("A_Link", u"Only Tab", id_1_, /*position=*/0)};
+    std::vector<SavedTabGroupTab> group_1_tabs = {test::CreateSavedTabGroupTab(
+        "A_Link", u"Only Tab", id_1_, /*position=*/0)};
     std::vector<SavedTabGroupTab> group_2_tabs = {
-        CreateSavedTabGroupTab("One_Link", u"One Of Two", id_2_,
-                               /*position=*/0),
-        CreateSavedTabGroupTab("Two_Link", u"Second", id_2_, /*position=*/1)};
+        test::CreateSavedTabGroupTab("One_Link", u"One Of Two", id_2_,
+                                     /*position=*/0),
+        test::CreateSavedTabGroupTab("Two_Link", u"Second", id_2_,
+                                     /*position=*/1)};
     std::vector<SavedTabGroupTab> group_3_tabs = {
-        CreateSavedTabGroupTab("Athos", u"All For One", id_3_,
-                               /*position=*/0),
-        CreateSavedTabGroupTab("Porthos", u"And", id_3_, /*position=*/1),
-        CreateSavedTabGroupTab("Aramis", u"One For All", id_3_,
-                               /*position=*/2)};
+        test::CreateSavedTabGroupTab("Athos", u"All For One", id_3_,
+                                     /*position=*/0),
+        test::CreateSavedTabGroupTab("Porthos", u"And", id_3_, /*position=*/1),
+        test::CreateSavedTabGroupTab("Aramis", u"One For All", id_3_,
+                                     /*position=*/2)};
 
     saved_tab_group_model_->Add(
         SavedTabGroup(title_1, color_1, group_1_tabs, std::nullopt, id_1_));
@@ -344,9 +270,9 @@
   const tab_groups::TabGroupColorId& color_4 =
       tab_groups::TabGroupColorId::kBlue;
 
-  SavedTabGroupTab tab1 = CreateSavedTabGroupTab(
+  SavedTabGroupTab tab1 = test::CreateSavedTabGroupTab(
       "4th group", u"First Tab 4th Group", id_4, /*position=*/0);
-  SavedTabGroupTab tab2 = CreateSavedTabGroupTab(
+  SavedTabGroupTab tab2 = test::CreateSavedTabGroupTab(
       "2nd link", u"Second Tab 4th Group", id_4, /*position=*/1);
 
   std::vector<SavedTabGroupTab> group_4_tabs = {tab1, tab2};
@@ -361,7 +287,7 @@
   EXPECT_EQ(saved_group->saved_guid(), id_4);
   EXPECT_EQ(saved_group->title(), title_4);
   EXPECT_EQ(saved_group->color(), color_4);
-  CompareSavedTabGroupTabs(saved_group->saved_tabs(), group_4_tabs);
+  test::CompareSavedTabGroupTabs(saved_group->saved_tabs(), group_4_tabs);
 }
 
 // Tests that SavedTabGroupModel::Update updates the correct element if the
@@ -411,9 +337,9 @@
 
 // Tests that the correct tabs are added to the correct position in group 1.
 TEST_P(SavedTabGroupModelTest, AddTabToGroup) {
-  SavedTabGroupTab tab1 = CreateSavedTabGroupTab(
+  SavedTabGroupTab tab1 = test::CreateSavedTabGroupTab(
       "4th group", u"First Tab 4th Group", id_1_, /*position=*/0);
-  SavedTabGroupTab tab2 = CreateSavedTabGroupTab(
+  SavedTabGroupTab tab2 = test::CreateSavedTabGroupTab(
       "2nd link", u"Second Tab 4th Group", id_1_, /*position=*/2);
 
   const SavedTabGroup* group = saved_tab_group_model_->Get(id_1_);
@@ -424,23 +350,25 @@
   EXPECT_EQ(0, group->GetIndexOfTab(tab1.saved_tab_guid()));
   EXPECT_TRUE(group->ContainsTab(tab1.saved_tab_guid()));
   ASSERT_TRUE(group->GetTab(tab1.saved_tab_guid()));
-  CompareSavedTabGroupTabs({*group->GetTab(tab1.saved_tab_guid())}, {tab1});
+  test::CompareSavedTabGroupTabs({*group->GetTab(tab1.saved_tab_guid())},
+                                 {tab1});
 
   saved_tab_group_model_->AddTabToGroupLocally(group->saved_guid(), tab2);
   EXPECT_EQ(group->saved_tabs().size(), size_t(3));
   EXPECT_EQ(2, group->GetIndexOfTab(tab2.saved_tab_guid()));
   EXPECT_TRUE(group->ContainsTab(tab2.saved_tab_guid()));
   ASSERT_TRUE(group->GetTab(tab2.saved_tab_guid()));
-  CompareSavedTabGroupTabs({*group->GetTab(tab2.saved_tab_guid())}, {tab2});
-  CompareSavedTabGroupTabs(group->saved_tabs(),
-                           {tab1, group->saved_tabs()[1], tab2});
+  test::CompareSavedTabGroupTabs({*group->GetTab(tab2.saved_tab_guid())},
+                                 {tab2});
+  test::CompareSavedTabGroupTabs(group->saved_tabs(),
+                                 {tab1, group->saved_tabs()[1], tab2});
 }
 
 // Tests that the correct tabs are removed from the correct position in group 1.
 TEST_P(SavedTabGroupModelTest, RemoveTabFromGroup) {
-  SavedTabGroupTab tab1 = CreateSavedTabGroupTab(
+  SavedTabGroupTab tab1 = test::CreateSavedTabGroupTab(
       "4th group", u"First Tab 4th Group", id_1_, /*position=*/0);
-  SavedTabGroupTab tab2 = CreateSavedTabGroupTab(
+  SavedTabGroupTab tab2 = test::CreateSavedTabGroupTab(
       "2nd link", u"Second Tab 4th Group", id_1_, /*position=*/2);
 
   const SavedTabGroup* group = saved_tab_group_model_->Get(id_1_);
@@ -453,12 +381,13 @@
   saved_tab_group_model_->RemoveTabFromGroupLocally(group->saved_guid(),
                                                     tab1.saved_tab_guid());
   EXPECT_EQ(group->saved_tabs().size(), size_t(2));
-  CompareSavedTabGroupTabs(group->saved_tabs(), {group->saved_tabs()[0], tab2});
+  test::CompareSavedTabGroupTabs(group->saved_tabs(),
+                                 {group->saved_tabs()[0], tab2});
 
   saved_tab_group_model_->RemoveTabFromGroupLocally(group->saved_guid(),
                                                     tab2.saved_tab_guid());
   EXPECT_EQ(group->saved_tabs().size(), size_t(1));
-  CompareSavedTabGroupTabs(group->saved_tabs(), {group->saved_tabs()[0]});
+  test::CompareSavedTabGroupTabs(group->saved_tabs(), {group->saved_tabs()[0]});
 }
 
 // Tests that a group is removed from the model when the last tab is removed
@@ -484,14 +413,14 @@
   saved_tab_group_model_->UpdateTabInGroup(id_1_, tab1);
 
   // The group should contain the updated tab.
-  CompareSavedTabGroupTabs(group->saved_tabs(), {tab1});
+  test::CompareSavedTabGroupTabs(group->saved_tabs(), {tab1});
 }
 
 // Tests that the correct tabs are moved in group 1.
 TEST_P(SavedTabGroupModelTest, MoveTabInGroup) {
-  SavedTabGroupTab tab1 = CreateSavedTabGroupTab(
+  SavedTabGroupTab tab1 = test::CreateSavedTabGroupTab(
       "4th group", u"First Tab 4th Group", id_1_, /*position=*/0);
-  SavedTabGroupTab tab2 = CreateSavedTabGroupTab(
+  SavedTabGroupTab tab2 = test::CreateSavedTabGroupTab(
       "2nd link", u"Second Tab 4th Group", id_1_, /*position=*/2);
 
   const SavedTabGroup* group = saved_tab_group_model_->Get(id_1_);
@@ -503,13 +432,13 @@
 
   saved_tab_group_model_->MoveTabInGroupTo(group->saved_guid(),
                                            tab1.saved_tab_guid(), 2);
-  CompareSavedTabGroupTabs(group->saved_tabs(),
-                           {group->saved_tabs()[0], tab2, tab1});
+  test::CompareSavedTabGroupTabs(group->saved_tabs(),
+                                 {group->saved_tabs()[0], tab2, tab1});
 
   saved_tab_group_model_->MoveTabInGroupTo(group->saved_guid(),
                                            tab1.saved_tab_guid(), 1);
-  CompareSavedTabGroupTabs(group->saved_tabs(),
-                           {group->saved_tabs()[0], tab1, tab2});
+  test::CompareSavedTabGroupTabs(group->saved_tabs(),
+                                 {group->saved_tabs()[0], tab1, tab2});
 }
 
 TEST_P(SavedTabGroupModelTest, MoveElement) {
@@ -572,8 +501,8 @@
   EXPECT_EQ(saved_group->title(), group->title());
   EXPECT_EQ(saved_group->color(), group->color());
 
-  // We can not use CompareSavedTabGroupTabs because the favicons are not loaded
-  // until the tab is opened through the saved group button.
+  // We can not use test::CompareSavedTabGroupTabs because the favicons are not
+  // loaded until the tab is opened through the saved group button.
   EXPECT_EQ(saved_group->saved_tabs().size(), group->saved_tabs().size());
 }
 
@@ -647,7 +576,7 @@
 
   EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
   for (size_t i = 0; i < groups.size(); ++i) {
-    EXPECT_TRUE(CompareSavedTabGroups(
+    EXPECT_TRUE(test::CompareSavedTabGroups(
         groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
   }
 }
@@ -683,7 +612,7 @@
 
   EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
   for (size_t i = 0; i < groups.size(); ++i) {
-    EXPECT_TRUE(CompareSavedTabGroups(
+    EXPECT_TRUE(test::CompareSavedTabGroups(
         groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
   }
 }
@@ -720,7 +649,7 @@
 
   EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
   for (size_t i = 0; i < groups.size(); ++i) {
-    EXPECT_TRUE(CompareSavedTabGroups(
+    EXPECT_TRUE(test::CompareSavedTabGroups(
         groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
   }
 }
@@ -757,7 +686,7 @@
 
   EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
   for (size_t i = 0; i < groups.size(); ++i) {
-    EXPECT_TRUE(CompareSavedTabGroups(
+    EXPECT_TRUE(test::CompareSavedTabGroups(
         groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
   }
 }
@@ -782,7 +711,7 @@
 
   EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
   for (size_t i = 0; i < groups.size(); ++i) {
-    EXPECT_TRUE(CompareSavedTabGroups(
+    EXPECT_TRUE(test::CompareSavedTabGroups(
         groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
   }
 }
@@ -831,7 +760,7 @@
             groups[5].position());
 
   for (size_t i = 0; i < groups.size(); ++i) {
-    EXPECT_TRUE(CompareSavedTabGroups(
+    EXPECT_TRUE(test::CompareSavedTabGroups(
         groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
   }
 }
@@ -913,7 +842,7 @@
 // Tests that SavedTabGroupModelObserver::Added passes the correct element from
 // the model.
 TEST_P(SavedTabGroupModelObserverTest, AddElement) {
-  SavedTabGroup group_4(CreateTestSavedTabGroup());
+  SavedTabGroup group_4(test::CreateTestSavedTabGroup());
   saved_tab_group_model_->Add(group_4);
 
   const int index = retrieved_group_.size() - 1;
@@ -923,7 +852,8 @@
   EXPECT_EQ(group_4.local_group_id(), received_group.local_group_id());
   EXPECT_EQ(group_4.title(), received_group.title());
   EXPECT_EQ(group_4.color(), received_group.color());
-  CompareSavedTabGroupTabs(group_4.saved_tabs(), received_group.saved_tabs());
+  test::CompareSavedTabGroupTabs(group_4.saved_tabs(),
+                                 received_group.saved_tabs());
   EXPECT_EQ(saved_tab_group_model_->GetIndexOf(received_group.saved_guid()),
             retrieved_index_);
 }
@@ -931,7 +861,7 @@
 // Tests that SavedTabGroupModelObserver::Removed passes the correct
 // element from the model.
 TEST_P(SavedTabGroupModelObserverTest, RemovedElement) {
-  SavedTabGroup group_4(CreateTestSavedTabGroup());
+  SavedTabGroup group_4(test::CreateTestSavedTabGroup());
   saved_tab_group_model_->Add(group_4);
   saved_tab_group_model_->Remove(group_4.saved_guid());
 
@@ -947,7 +877,7 @@
 // Tests that SavedTabGroupModelObserver::Updated passes the correct
 // element from the model.
 TEST_P(SavedTabGroupModelObserverTest, UpdatedElement) {
-  SavedTabGroup group_4(CreateTestSavedTabGroup());
+  SavedTabGroup group_4(test::CreateTestSavedTabGroup());
   saved_tab_group_model_->Add(group_4);
 
   const std::u16string new_title = u"New Title";
@@ -966,7 +896,8 @@
   EXPECT_EQ(group_4.local_group_id(), received_group.local_group_id());
   EXPECT_EQ(new_title, received_group.title());
   EXPECT_EQ(new_color, received_group.color());
-  CompareSavedTabGroupTabs(group_4.saved_tabs(), received_group.saved_tabs());
+  test::CompareSavedTabGroupTabs(group_4.saved_tabs(),
+                                 received_group.saved_tabs());
   EXPECT_EQ(saved_tab_group_model_->GetIndexOf(received_group.saved_guid()),
             retrieved_index_);
 }
@@ -974,7 +905,7 @@
 // Tests that SavedTabGroupModelObserver::AddedFromSync passes the correct
 // element from the model.
 TEST_P(SavedTabGroupModelObserverTest, AddElementFromSync) {
-  SavedTabGroup group_4(CreateTestSavedTabGroup());
+  SavedTabGroup group_4(test::CreateTestSavedTabGroup());
   group_4.SetPosition(0);
   saved_tab_group_model_->AddedFromSync(group_4);
 
@@ -985,7 +916,8 @@
   EXPECT_EQ(group_4.local_group_id(), received_group.local_group_id());
   EXPECT_EQ(group_4.title(), received_group.title());
   EXPECT_EQ(group_4.color(), received_group.color());
-  CompareSavedTabGroupTabs(group_4.saved_tabs(), received_group.saved_tabs());
+  test::CompareSavedTabGroupTabs(group_4.saved_tabs(),
+                                 received_group.saved_tabs());
   EXPECT_EQ(saved_tab_group_model_->GetIndexOf(received_group.saved_guid()),
             retrieved_index_);
 }
@@ -993,7 +925,7 @@
 // Tests that SavedTabGroupModelObserver::RemovedFromSync passes the correct
 // element from the model.
 TEST_P(SavedTabGroupModelObserverTest, RemovedElementFromSync) {
-  SavedTabGroup group_4(CreateTestSavedTabGroup());
+  SavedTabGroup group_4(test::CreateTestSavedTabGroup());
   saved_tab_group_model_->Add(group_4);
   saved_tab_group_model_->RemovedFromSync(group_4.saved_guid());
 
@@ -1009,7 +941,7 @@
 // Tests that SavedTabGroupModelObserver::UpdatedFromSync passes the correct
 // element from the model.
 TEST_P(SavedTabGroupModelObserverTest, UpdatedElementFromSync) {
-  SavedTabGroup group_4(CreateTestSavedTabGroup());
+  SavedTabGroup group_4(test::CreateTestSavedTabGroup());
   saved_tab_group_model_->Add(group_4);
 
   const std::u16string new_title = u"New Title";
@@ -1028,7 +960,8 @@
   EXPECT_EQ(group_4.local_group_id(), received_group.local_group_id());
   EXPECT_EQ(new_title, received_group.title());
   EXPECT_EQ(new_color, received_group.color());
-  CompareSavedTabGroupTabs(group_4.saved_tabs(), received_group.saved_tabs());
+  test::CompareSavedTabGroupTabs(group_4.saved_tabs(),
+                                 received_group.saved_tabs());
   EXPECT_EQ(saved_tab_group_model_->GetIndexOf(received_group.saved_guid()),
             retrieved_index_);
 }
@@ -1036,8 +969,8 @@
 // Verify that SavedTabGroupModel::OnGroupClosedInTabStrip passes the correct
 // index.
 TEST_P(SavedTabGroupModelObserverTest, OnGroupClosedInTabStrip) {
-  SavedTabGroup group_4 = CreateTestSavedTabGroup();
-  LocalTabGroupID tab_group_id = GenerateRandomTabGroupID();
+  SavedTabGroup group_4 = test::CreateTestSavedTabGroup();
+  LocalTabGroupID tab_group_id = test::GenerateRandomTabGroupID();
   group_4.SetLocalGroupId(tab_group_id);
   saved_tab_group_model_->Add(group_4);
   const int index =
@@ -1090,7 +1023,7 @@
 }
 
 TEST_P(SavedTabGroupModelObserverTest, ReordedTabsUpdatePositions) {
-  SavedTabGroup group = CreateTestSavedTabGroup();
+  SavedTabGroup group = test::CreateTestSavedTabGroup();
   base::Uuid group_id = group.saved_guid();
   base::Uuid tab1_id = group.saved_tabs()[0].saved_tab_guid();
   base::Uuid tab2_id = group.saved_tabs()[1].saved_tab_guid();
@@ -1106,14 +1039,14 @@
 
 TEST_P(SavedTabGroupModelObserverTest, GetGroupContainingTab) {
   // Add a non matching SavedTabGroup.
-  saved_tab_group_model_->Add(CreateTestSavedTabGroup());
+  saved_tab_group_model_->Add(test::CreateTestSavedTabGroup());
 
   // Add a matching group/tab and save the ids used for GetGroupContainingTab.
-  SavedTabGroup matching_group = CreateTestSavedTabGroup();
+  SavedTabGroup matching_group = test::CreateTestSavedTabGroup();
   base::Uuid matching_group_guid = matching_group.saved_guid();
 
   base::Uuid matching_tab_guid = base::Uuid::GenerateRandomV4();
-  LocalTabID matching_local_tab_id = GenerateRandomTabID();
+  LocalTabID matching_local_tab_id = test::GenerateRandomTabID();
 
   SavedTabGroupTab tab(GURL(url::kAboutBlankURL), std::u16string(u"title"),
                        matching_group.saved_guid(), /*position=*/std::nullopt,
@@ -1122,7 +1055,7 @@
   saved_tab_group_model_->Add(std::move(matching_group));
 
   // Add another non matching SavedTabGroup.
-  saved_tab_group_model_->Add(CreateTestSavedTabGroup());
+  saved_tab_group_model_->Add(test::CreateTestSavedTabGroup());
   ASSERT_EQ(3, saved_tab_group_model_->Count());
 
   // call GetGroupContainingTab with the 2 ids and expect them to return.
@@ -1145,7 +1078,7 @@
     GTEST_SKIP() << "N/A for V1";
   }
 
-  SavedTabGroup group(CreateTestSavedTabGroup());
+  SavedTabGroup group(test::CreateTestSavedTabGroup());
   saved_tab_group_model_->Add(group);
 
   saved_tab_group_model_->TogglePinState(group.saved_guid());
diff --git a/components/saved_tab_groups/saved_tab_group_test_utils.cc b/components/saved_tab_groups/saved_tab_group_test_utils.cc
new file mode 100644
index 0000000..0cf8af84
--- /dev/null
+++ b/components/saved_tab_groups/saved_tab_group_test_utils.cc
@@ -0,0 +1,101 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/saved_tab_groups/saved_tab_group_test_utils.h"
+
+#include "base/rand_util.h"
+#include "base/token.h"
+#include "build/build_config.h"
+#include "components/tab_groups/tab_group_color.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/url_constants.h"
+
+namespace tab_groups::test {
+
+LocalTabGroupID GenerateRandomTabGroupID() {
+#if BUILDFLAG(IS_ANDROID)
+  return base::RandInt(0, 1000);
+#else
+  return tab_groups::TabGroupId::GenerateNew();
+#endif
+}
+
+LocalTabID GenerateRandomTabID() {
+#if BUILDFLAG(IS_ANDROID)
+  return base::RandInt(0, 1000);
+#else
+  return base::Token::CreateRandom();
+#endif
+}
+
+void CompareSavedTabGroupTabs(const std::vector<SavedTabGroupTab>& v1,
+                              const std::vector<SavedTabGroupTab>& v2) {
+  ASSERT_EQ(v1.size(), v2.size());
+  for (size_t i = 0; i < v1.size(); i++) {
+    SavedTabGroupTab tab1 = v1[i];
+    SavedTabGroupTab tab2 = v2[i];
+    EXPECT_EQ(tab1.url(), tab2.url());
+    EXPECT_EQ(tab1.title(), tab2.title());
+    EXPECT_EQ(tab1.favicon(), tab2.favicon());
+  }
+}
+
+bool CompareSavedTabGroups(const SavedTabGroup& g1, const SavedTabGroup& g2) {
+  if (g1.title() != g2.title()) {
+    return false;
+  }
+  if (g1.color() != g2.color()) {
+    return false;
+  }
+  if (g1.position() != g2.position()) {
+    return false;
+  }
+  if (g1.saved_guid() != g2.saved_guid()) {
+    return false;
+  }
+  if (g1.creation_time_windows_epoch_micros() !=
+      g2.creation_time_windows_epoch_micros()) {
+    return false;
+  }
+
+  return true;
+}
+
+SavedTabGroupTab CreateSavedTabGroupTab(const std::string& url,
+                                        const std::u16string& title,
+                                        const base::Uuid& group_guid,
+                                        std::optional<int> position) {
+  SavedTabGroupTab tab(GURL(url), title, group_guid, position);
+  tab.SetFavicon(gfx::Image());
+  return tab;
+}
+
+SavedTabGroup CreateTestSavedTabGroup() {
+  base::Uuid id = base::Uuid::GenerateRandomV4();
+  const std::u16string title = u"Test Test";
+  const tab_groups::TabGroupColorId& color = tab_groups::TabGroupColorId::kBlue;
+
+  SavedTabGroupTab tab1 =
+      CreateSavedTabGroupTab("www.google.com", u"Google", id, /*position=*/0);
+  SavedTabGroupTab tab2 =
+      CreateSavedTabGroupTab("chrome://newtab", u"new tab", id, /*position=*/1);
+
+  tab1.SetFavicon(gfx::Image());
+  tab2.SetFavicon(gfx::Image());
+
+  std::vector<SavedTabGroupTab> tabs = {tab1, tab2};
+
+  SavedTabGroup group(title, color, tabs, std::nullopt, id);
+  return group;
+}
+
+TabGroupVisualData CreateTabGroupVisualData() {
+  const std::u16string title = u"Visuals Test";
+  const tab_groups::TabGroupColorId& color =
+      tab_groups::TabGroupColorId::kOrange;
+  return TabGroupVisualData(title, color);
+}
+
+}  // namespace tab_groups::test
diff --git a/components/saved_tab_groups/saved_tab_group_test_utils.h b/components/saved_tab_groups/saved_tab_group_test_utils.h
new file mode 100644
index 0000000..2dade5d
--- /dev/null
+++ b/components/saved_tab_groups/saved_tab_group_test_utils.h
@@ -0,0 +1,42 @@
+// 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 COMPONENTS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_TEST_UTILS_H_
+#define COMPONENTS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_TEST_UTILS_H_
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "base/uuid.h"
+#include "components/saved_tab_groups/saved_tab_group.h"
+#include "components/saved_tab_groups/saved_tab_group_tab.h"
+#include "components/saved_tab_groups/types.h"
+#include "components/tab_groups/tab_group_visual_data.h"
+
+namespace tab_groups::test {
+
+// Utility methods to generate IDs.
+LocalTabGroupID GenerateRandomTabGroupID();
+LocalTabID GenerateRandomTabID();
+
+// Comparison methods. Note, some of the properties, such as position etc are
+// generated automatically by the model when the caller passes std::nullopt and
+// hence might differ from the value passed in to the model.
+void CompareSavedTabGroupTabs(const std::vector<SavedTabGroupTab>& v1,
+                              const std::vector<SavedTabGroupTab>& v2);
+bool CompareSavedTabGroups(const SavedTabGroup& g1, const SavedTabGroup& g2);
+
+// Helper method to create saved tabs and groups.
+SavedTabGroupTab CreateSavedTabGroupTab(const std::string& url,
+                                        const std::u16string& title,
+                                        const base::Uuid& group_guid,
+                                        std::optional<int> position);
+SavedTabGroup CreateTestSavedTabGroup();
+TabGroupVisualData CreateTabGroupVisualData();
+
+}  // namespace tab_groups::test
+
+#endif  // COMPONENTS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_TEST_UTILS_H_
diff --git a/components/saved_tab_groups/tab_group_sync_service.h b/components/saved_tab_groups/tab_group_sync_service.h
index 0e55bd1..0818614 100644
--- a/components/saved_tab_groups/tab_group_sync_service.h
+++ b/components/saved_tab_groups/tab_group_sync_service.h
@@ -16,7 +16,9 @@
 #include "components/saved_tab_groups/saved_tab_group.h"
 #include "components/sync/model/model_type_sync_bridge.h"
 #include "components/tab_groups/tab_group_id.h"
+#include "components/tab_groups/tab_group_visual_data.h"
 #include "ui/gfx/range/range.h"
+#include "url/gurl.h"
 
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/jni_android.h"
@@ -44,13 +46,21 @@
   // either the local or remote clients.
   class Observer : public base::CheckedObserver {
    public:
-    // A new tab group was added, or an existing tab group was updated at the
-    // given |source|.
-    virtual void OnTabGroupAddedOrUpdated(const SavedTabGroup& group,
-                                          TriggerSource source) = 0;
+    // The data from sync ModelTypeStore has been loaded to memory.
+    virtual void OnInitialized() = 0;
+
+    // A new tab group was added at the given |source|.
+    virtual void OnTabGroupAdded(const SavedTabGroup& group,
+                                 TriggerSource source) = 0;
+
+    // An existing tab group was updated at the given |source|.
+    // Called whenever there are an update to a tab group, which can be title,
+    // color, position, pinned state, or update to any of its tabs.
+    virtual void OnTabGroupUpdated(const SavedTabGroup& group,
+                                   TriggerSource source) = 0;
 
     // Tab group corresponding to the |local_id| was removed.
-    virtual void OnTabGroupRemoved(const tab_groups::TabGroupId& local_id) = 0;
+    virtual void OnTabGroupRemoved(const LocalTabGroupID& local_id) = 0;
   };
 
 #if BUILDFLAG(IS_ANDROID)
@@ -71,19 +81,39 @@
   // The service will notify the observers accordingly, i.e. notify sync to
   // propagate the changes to server side, and notify any UI observers such
   // as revisit surface to update their UI accordingly.
-  virtual void AddOrUpdateGroup(SavedTabGroup group) = 0;
-  virtual void RemoveGroup(const LocalTabGroupID& local_id) = 0;
 
-  // Get methods.
+  // Mutator methods that result in group metadata mutation.
+  virtual void AddGroup(const SavedTabGroup& group) = 0;
+  virtual void RemoveGroup(const LocalTabGroupID& local_id) = 0;
+  virtual void UpdateVisualData(
+      const LocalTabGroupID local_group_id,
+      const tab_groups::TabGroupVisualData* visual_data) = 0;
+
+  // Mutator methods that result in tab metadata mutation.
+  virtual void AddTab(const LocalTabGroupID& group_id,
+                      const LocalTabID& tab_id,
+                      const std::u16string& title,
+                      GURL url,
+                      std::optional<size_t> position) = 0;
+  virtual void UpdateTab(const LocalTabGroupID& group_id,
+                         const LocalTabID& tab_id,
+                         const std::u16string& title,
+                         GURL url,
+                         std::optional<size_t> position) = 0;
+  virtual void RemoveTab(const LocalTabGroupID& group_id,
+                         const LocalTabID& tab_id) = 0;
+
+  // Accessor methods.
   virtual std::vector<SavedTabGroup> GetAllGroups() = 0;
   virtual std::optional<SavedTabGroup> GetGroup(const base::Uuid& guid) = 0;
   virtual std::optional<SavedTabGroup> GetGroup(LocalTabGroupID& local_id) = 0;
 
-  // Book-keeping methods to map the IDs.
-  virtual void SetLocalTabGroupIdForSyncId(const base::Uuid& sync_id,
-                                           LocalTabGroupID& local_id) = 0;
-  virtual base::Uuid GetSyncIdForLocalTabGroupId(LocalTabGroupID& local_id) = 0;
-  virtual base::Uuid GetLocalIdForSyncId(const base::Uuid& sync_id) = 0;
+  // Book-keeping methods to maintain in-memory mapping of sync and local IDs.
+  virtual void UpdateLocalTabGroupId(const base::Uuid& sync_id,
+                                     const LocalTabGroupID& local_id) = 0;
+  virtual void UpdateLocalTabId(const LocalTabGroupID& local_group_id,
+                                const base::Uuid& sync_tab_id,
+                                const LocalTabID& local_tab_id) = 0;
 
   // For connecting to sync engine.
   virtual syncer::ModelTypeSyncBridge* bridge() = 0;
diff --git a/components/saved_tab_groups/tab_group_sync_service_impl.cc b/components/saved_tab_groups/tab_group_sync_service_impl.cc
index 4803cd6..486ea3a8 100644
--- a/components/saved_tab_groups/tab_group_sync_service_impl.cc
+++ b/components/saved_tab_groups/tab_group_sync_service_impl.cc
@@ -32,6 +32,12 @@
 void TabGroupSyncServiceImpl::AddObserver(
     TabGroupSyncService::Observer* observer) {
   observers_.AddObserver(observer);
+
+  // If the observer is added late and missed the init signal, send the signal
+  // now.
+  if (model_->is_loaded()) {
+    observer->OnInitialized();
+  }
 }
 
 void TabGroupSyncServiceImpl::RemoveObserver(
@@ -43,50 +49,124 @@
   return &bridge_;
 }
 
-void TabGroupSyncServiceImpl::AddOrUpdateGroup(SavedTabGroup group) {}
+void TabGroupSyncServiceImpl::AddGroup(const SavedTabGroup& group) {
+  model_->Add(group);
+}
 
-void TabGroupSyncServiceImpl::RemoveGroup(const LocalTabGroupID& local_id) {}
+void TabGroupSyncServiceImpl::RemoveGroup(const LocalTabGroupID& local_id) {
+  model_->Remove(local_id);
+}
+
+void TabGroupSyncServiceImpl::UpdateVisualData(
+    const LocalTabGroupID local_group_id,
+    const tab_groups::TabGroupVisualData* visual_data) {
+  model_->UpdateVisualData(local_group_id, visual_data);
+}
+
+void TabGroupSyncServiceImpl::AddTab(const LocalTabGroupID& group_id,
+                                     const LocalTabID& tab_id,
+                                     const std::u16string& title,
+                                     GURL url,
+                                     std::optional<size_t> position) {
+  auto* group = model_->Get(group_id);
+  if (!group) {
+    return;
+  }
+
+  const auto* tab = group->GetTab(tab_id);
+  if (tab) {
+    return;
+  }
+
+  SavedTabGroupTab new_tab(url, title, group->saved_guid(), position,
+                           /*saved_tab_guid=*/std::nullopt, tab_id);
+  model_->AddTabToGroupLocally(group->saved_guid(), new_tab);
+}
+
+void TabGroupSyncServiceImpl::UpdateTab(const LocalTabGroupID& group_id,
+                                        const LocalTabID& tab_id,
+                                        const std::u16string& title,
+                                        GURL url,
+                                        std::optional<size_t> position) {
+  auto* group = model_->Get(group_id);
+  if (!group) {
+    return;
+  }
+
+  const auto* tab = group->GetTab(tab_id);
+  if (!tab) {
+    return;
+  }
+
+  SavedTabGroupTab updated_tab(*tab);
+  updated_tab.SetLocalTabID(tab_id);
+  updated_tab.SetTitle(title);
+  updated_tab.SetURL(url);
+  if (position.has_value()) {
+    updated_tab.SetPosition(position.value());
+  }
+  model_->UpdateTabInGroup(group->saved_guid(), updated_tab);
+}
+
+void TabGroupSyncServiceImpl::RemoveTab(const LocalTabGroupID& group_id,
+                                        const LocalTabID& tab_id) {
+  auto* group = model_->Get(group_id);
+  if (!group) {
+    return;
+  }
+
+  auto* tab = group->GetTab(tab_id);
+  if (!tab) {
+    return;
+  }
+
+  model_->RemoveTabFromGroupLocally(group->saved_guid(), tab->saved_tab_guid());
+}
 
 std::vector<SavedTabGroup> TabGroupSyncServiceImpl::GetAllGroups() {
-  // TODO(b/326546431): Implement.
-  return std::vector<SavedTabGroup>();
+  return model_->saved_tab_groups();
 }
 
 std::optional<SavedTabGroup> TabGroupSyncServiceImpl::GetGroup(
     const base::Uuid& guid) {
-  // TODO(b/326546431): Implement.
-  return std::nullopt;
+  const SavedTabGroup* tab_group = model_->Get(guid);
+  return tab_group ? std::make_optional<SavedTabGroup>(*tab_group)
+                   : std::nullopt;
 }
 
 std::optional<SavedTabGroup> TabGroupSyncServiceImpl::GetGroup(
     LocalTabGroupID& local_id) {
-  // TODO(b/326546431): Implement.
-  return std::nullopt;
+  const SavedTabGroup* tab_group = model_->Get(local_id);
+  return tab_group ? std::make_optional<SavedTabGroup>(*tab_group)
+                   : std::nullopt;
 }
 
-void TabGroupSyncServiceImpl::SetLocalTabGroupIdForSyncId(
+void TabGroupSyncServiceImpl::UpdateLocalTabGroupId(
     const base::Uuid& sync_id,
-    LocalTabGroupID& local_id) {
-  // TODO(b/326546431): Implement.
+    const LocalTabGroupID& local_id) {
+  model_->OnGroupOpenedInTabStrip(sync_id, local_id);
 }
 
-base::Uuid TabGroupSyncServiceImpl::GetSyncIdForLocalTabGroupId(
-    LocalTabGroupID& local_id) {
-  // TODO(b/326546431): Implement.
-  return base::Uuid();
-}
+void TabGroupSyncServiceImpl::UpdateLocalTabId(
+    const LocalTabGroupID& local_group_id,
+    const base::Uuid& sync_tab_id,
+    const LocalTabID& local_tab_id) {
+  auto* group = model_->Get(local_group_id);
+  CHECK(group);
 
-base::Uuid TabGroupSyncServiceImpl::GetLocalIdForSyncId(
-    const base::Uuid& sync_id) {
-  // TODO(b/326546431): Implement.
-  return base::Uuid();
+  const auto* tab = group->GetTab(sync_tab_id);
+  CHECK(tab);
+
+  model_->UpdateLocalTabId(group->saved_guid(), *tab, local_tab_id);
 }
 
 void TabGroupSyncServiceImpl::SavedTabGroupAddedFromSync(
     const base::Uuid& guid) {
   const SavedTabGroup* saved_tab_group = model_->Get(guid);
   CHECK(saved_tab_group);
-  // TODO(b/326546431): Implement.
+  for (auto& observer : observers_) {
+    observer.OnTabGroupAdded(*saved_tab_group, TriggerSource::REMOTE);
+  }
 }
 
 void TabGroupSyncServiceImpl::SavedTabGroupUpdatedFromSync(
@@ -94,7 +174,27 @@
     const std::optional<base::Uuid>& tab_guid) {
   const SavedTabGroup* saved_tab_group = model_->Get(group_guid);
   CHECK(saved_tab_group);
-  // TODO(b/326546431): Implement.
+  for (auto& observer : observers_) {
+    observer.OnTabGroupUpdated(*saved_tab_group, TriggerSource::REMOTE);
+  }
+}
+
+void TabGroupSyncServiceImpl::SavedTabGroupRemovedFromSync(
+    const SavedTabGroup* removed_group) {
+  auto local_id = removed_group->local_group_id();
+  if (!local_id.has_value()) {
+    return;
+  }
+
+  for (auto& observer : observers_) {
+    observer.OnTabGroupRemoved(local_id.value());
+  }
+}
+
+void TabGroupSyncServiceImpl::SavedTabGroupModelLoaded() {
+  for (auto& observer : observers_) {
+    observer.OnInitialized();
+  }
 }
 
 }  // namespace tab_groups
diff --git a/components/saved_tab_groups/tab_group_sync_service_impl.h b/components/saved_tab_groups/tab_group_sync_service_impl.h
index 41060784..f2296ae0 100644
--- a/components/saved_tab_groups/tab_group_sync_service_impl.h
+++ b/components/saved_tab_groups/tab_group_sync_service_impl.h
@@ -37,15 +37,31 @@
   TabGroupSyncServiceImpl& operator=(const TabGroupSyncServiceImpl&) = delete;
 
   // TabGroupSyncService implementation.
-  void AddOrUpdateGroup(SavedTabGroup group) override;
+  void AddGroup(const SavedTabGroup& group) override;
   void RemoveGroup(const LocalTabGroupID& local_id) override;
+  void UpdateVisualData(
+      const LocalTabGroupID local_group_id,
+      const tab_groups::TabGroupVisualData* visual_data) override;
+  void AddTab(const LocalTabGroupID& group_id,
+              const LocalTabID& tab_id,
+              const std::u16string& title,
+              GURL url,
+              std::optional<size_t> position) override;
+  void UpdateTab(const LocalTabGroupID& group_id,
+                 const LocalTabID& tab_id,
+                 const std::u16string& title,
+                 GURL url,
+                 std::optional<size_t> position) override;
+  void RemoveTab(const LocalTabGroupID& group_id,
+                 const LocalTabID& tab_id) override;
   std::vector<SavedTabGroup> GetAllGroups() override;
   std::optional<SavedTabGroup> GetGroup(const base::Uuid& guid) override;
   std::optional<SavedTabGroup> GetGroup(LocalTabGroupID& local_id) override;
-  void SetLocalTabGroupIdForSyncId(const base::Uuid& sync_id,
-                                   LocalTabGroupID& local_id) override;
-  base::Uuid GetSyncIdForLocalTabGroupId(LocalTabGroupID& local_id) override;
-  base::Uuid GetLocalIdForSyncId(const base::Uuid& sync_id) override;
+  void UpdateLocalTabGroupId(const base::Uuid& sync_id,
+                             const LocalTabGroupID& local_id) override;
+  void UpdateLocalTabId(const LocalTabGroupID& local_group_id,
+                        const base::Uuid& sync_tab_id,
+                        const LocalTabID& local_tab_id) override;
   syncer::ModelTypeSyncBridge* bridge() override;
   void AddObserver(TabGroupSyncService::Observer* observer) override;
   void RemoveObserver(TabGroupSyncService::Observer* observer) override;
@@ -56,6 +72,9 @@
   void SavedTabGroupUpdatedFromSync(
       const base::Uuid& group_guid,
       const std::optional<base::Uuid>& tab_guid) override;
+  void SavedTabGroupRemovedFromSync(
+      const SavedTabGroup* removed_group) override;
+  void SavedTabGroupModelLoaded() override;
 
   // The in-memory model representing the currently present saved tab groups.
   std::unique_ptr<SavedTabGroupModel> model_;
@@ -71,7 +90,7 @@
       saved_guid_to_local_group_id_mapping_;
 
   // Obsevers of the model.
-  base::ObserverList<TabGroupSyncService::Observer>::Unchecked observers_;
+  base::ObserverList<TabGroupSyncService::Observer> observers_;
 };
 
 }  // namespace tab_groups
diff --git a/components/saved_tab_groups/tab_group_sync_service_unittest.cc b/components/saved_tab_groups/tab_group_sync_service_unittest.cc
index de55179..f257d8ce 100644
--- a/components/saved_tab_groups/tab_group_sync_service_unittest.cc
+++ b/components/saved_tab_groups/tab_group_sync_service_unittest.cc
@@ -2,52 +2,327 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/saved_tab_groups/tab_group_sync_service_impl.h"
-
 #include <memory>
 
+#include "base/memory/raw_ptr.h"
 #include "base/test/task_environment.h"
 #include "components/saved_tab_groups/saved_tab_group_model.h"
+#include "components/saved_tab_groups/saved_tab_group_test_utils.h"
+#include "components/saved_tab_groups/tab_group_sync_service_impl.h"
 #include "components/sync/test/mock_model_type_change_processor.h"
 #include "components/sync/test/model_type_store_test_util.h"
 #include "components/sync/test/test_matchers.h"
+#include "components/tab_groups/tab_group_visual_data.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using testing::_;
+using testing::Eq;
+
 namespace tab_groups {
+namespace {
+
+class MockTabGroupSyncServiceObserver : public TabGroupSyncService::Observer {
+ public:
+  MockTabGroupSyncServiceObserver() = default;
+  ~MockTabGroupSyncServiceObserver() override = default;
+
+  MOCK_METHOD(void, OnInitialized, ());
+  MOCK_METHOD(void, OnTabGroupAdded, (const SavedTabGroup&, TriggerSource));
+  MOCK_METHOD(void, OnTabGroupUpdated, (const SavedTabGroup&, TriggerSource));
+  MOCK_METHOD(void, OnTabGroupRemoved, (const LocalTabGroupID&));
+};
+
+MATCHER_P(UuidEq, uuid, "") {
+  return arg.saved_guid() == uuid;
+}
+
+}  // namespace
 
 class TabGroupSyncServiceTest : public testing::Test {
  public:
   TabGroupSyncServiceTest()
-      : store_(syncer::ModelTypeStoreTestUtil::CreateInMemoryStoreForTest()) {}
+      : store_(syncer::ModelTypeStoreTestUtil::CreateInMemoryStoreForTest()),
+        group_1_(test::CreateTestSavedTabGroup()),
+        group_2_(test::CreateTestSavedTabGroup()),
+        group_3_(test::CreateTestSavedTabGroup()),
+        local_group_id_1_(test::GenerateRandomTabGroupID()),
+        local_tab_id_1_(test::GenerateRandomTabID()) {}
 
   ~TabGroupSyncServiceTest() override = default;
 
   void SetUp() override {
-    model_ = std::make_unique<SavedTabGroupModel>();
+    auto model = std::make_unique<SavedTabGroupModel>();
+    model_ = model.get();
     tab_group_sync_service_ = std::make_unique<TabGroupSyncServiceImpl>(
-        std::move(model_), processor_.CreateForwardingProcessor(),
+        std::move(model), processor_.CreateForwardingProcessor(),
         syncer::ModelTypeStoreTestUtil::FactoryForForwardingStore(
             store_.get()));
     ON_CALL(processor_, IsTrackingMetadata())
         .WillByDefault(testing::Return(true));
+    observer_ = std::make_unique<MockTabGroupSyncServiceObserver>();
+    tab_group_sync_service_->AddObserver(observer_.get());
     task_environment_.RunUntilIdle();
+
+    InitializeTestGroups();
   }
 
   testing::NiceMock<syncer::MockModelTypeChangeProcessor>* mock_processor() {
     return &processor_;
   }
 
+  void TearDown() override {
+    tab_group_sync_service_->RemoveObserver(observer_.get());
+    model_ = nullptr;
+  }
+
+  void InitializeTestGroups() {
+    base::Uuid id_1 = base::Uuid::GenerateRandomV4();
+    base::Uuid id_2 = base::Uuid::GenerateRandomV4();
+    base::Uuid id_3 = base::Uuid::GenerateRandomV4();
+
+    const std::u16string title_1 = u"Group One";
+    const std::u16string title_2 = u"Another Group";
+    const std::u16string title_3 = u"The Three Musketeers";
+
+    const tab_groups::TabGroupColorId& color_1 =
+        tab_groups::TabGroupColorId::kGrey;
+    const tab_groups::TabGroupColorId& color_2 =
+        tab_groups::TabGroupColorId::kRed;
+    const tab_groups::TabGroupColorId& color_3 =
+        tab_groups::TabGroupColorId::kGreen;
+
+    SavedTabGroupTab group_1_tab_1 = test::CreateSavedTabGroupTab(
+        "A_Link", u"Only Tab", id_1, /*position=*/0);
+    group_1_tab_1.SetLocalTabID(local_tab_id_1_);
+    std::vector<SavedTabGroupTab> group_1_tabs = {group_1_tab_1};
+    std::vector<SavedTabGroupTab> group_2_tabs = {
+        test::CreateSavedTabGroupTab("One_Link", u"One Of Two", id_2,
+                                     /*position=*/0),
+        test::CreateSavedTabGroupTab("Two_Link", u"Second", id_2,
+                                     /*position=*/1)};
+    std::vector<SavedTabGroupTab> group_3_tabs = {
+        test::CreateSavedTabGroupTab("Athos", u"All For One", id_3,
+                                     /*position=*/0),
+        test::CreateSavedTabGroupTab("Porthos", u"And", id_3, /*position=*/1),
+        test::CreateSavedTabGroupTab("Aramis", u"One For All", id_3,
+                                     /*position=*/2)};
+
+    group_1_ = SavedTabGroup(title_1, color_1, group_1_tabs, std::nullopt, id_1,
+                             local_group_id_1_);
+    group_2_ =
+        SavedTabGroup(title_2, color_2, group_2_tabs, std::nullopt, id_2);
+    group_3_ =
+        SavedTabGroup(title_3, color_3, group_3_tabs, std::nullopt, id_3);
+
+    model_->Add(group_1_);
+    model_->Add(group_2_);
+    model_->Add(group_3_);
+  }
+
  protected:
   base::test::TaskEnvironment task_environment_;
-  std::unique_ptr<SavedTabGroupModel> model_;
+  raw_ptr<SavedTabGroupModel> model_;
   testing::NiceMock<syncer::MockModelTypeChangeProcessor> processor_;
   std::unique_ptr<syncer::ModelTypeStore> store_;
+  std::unique_ptr<MockTabGroupSyncServiceObserver> observer_;
   std::unique_ptr<TabGroupSyncServiceImpl> tab_group_sync_service_;
+
+  SavedTabGroup group_1_;
+  SavedTabGroup group_2_;
+  SavedTabGroup group_3_;
+  LocalTabGroupID local_group_id_1_;
+  LocalTabID local_tab_id_1_;
 };
 
 TEST_F(TabGroupSyncServiceTest, ServiceConstruction) {
-  // TODO(b/326546431): Add more tests.
+  EXPECT_TRUE(tab_group_sync_service_->bridge());
+}
+
+TEST_F(TabGroupSyncServiceTest, GetAllGroups) {
+  auto all_groups = tab_group_sync_service_->GetAllGroups();
+  EXPECT_EQ(all_groups.size(), 3u);
+  EXPECT_EQ(all_groups[0].saved_guid(), group_1_.saved_guid());
+  EXPECT_EQ(all_groups[1].saved_guid(), group_2_.saved_guid());
+  EXPECT_EQ(all_groups[2].saved_guid(), group_3_.saved_guid());
+}
+
+TEST_F(TabGroupSyncServiceTest, GetGroup) {
+  auto group = tab_group_sync_service_->GetGroup(group_1_.saved_guid());
+  EXPECT_TRUE(group.has_value());
+
+  EXPECT_EQ(group->saved_guid(), group_1_.saved_guid());
+  EXPECT_EQ(group->title(), group_1_.title());
+  EXPECT_EQ(group->color(), group_1_.color());
+  test::CompareSavedTabGroupTabs(group->saved_tabs(), group_1_.saved_tabs());
+}
+
+TEST_F(TabGroupSyncServiceTest, AddGroup) {
+  // Add a new group.
+  SavedTabGroup group_4(test::CreateTestSavedTabGroup());
+  tab_group_sync_service_->AddGroup(group_4);
+
+  // Verify model internals.
+  EXPECT_TRUE(model_->Contains(group_4.saved_guid()));
+  EXPECT_EQ(model_->GetIndexOf(group_4.saved_guid()), 3);
+  EXPECT_EQ(model_->Count(), 4);
+
+  // Query the group via service and verify members.
+  auto group = tab_group_sync_service_->GetGroup(group_4.saved_guid());
+  EXPECT_TRUE(group.has_value());
+  EXPECT_EQ(group->saved_guid(), group_4.saved_guid());
+  EXPECT_EQ(group->title(), group_4.title());
+  EXPECT_EQ(group->color(), group_4.color());
+  test::CompareSavedTabGroupTabs(group->saved_tabs(), group_4.saved_tabs());
+}
+
+TEST_F(TabGroupSyncServiceTest, RemoveGroup) {
+  // Add a group.
+  SavedTabGroup group_4(test::CreateTestSavedTabGroup());
+  LocalTabGroupID tab_group_id = test::GenerateRandomTabGroupID();
+  group_4.SetLocalGroupId(tab_group_id);
+  tab_group_sync_service_->AddGroup(group_4);
+  EXPECT_TRUE(
+      tab_group_sync_service_->GetGroup(group_4.saved_guid()).has_value());
+
+  // Remove the group and verify.
+  tab_group_sync_service_->RemoveGroup(tab_group_id);
+  EXPECT_EQ(tab_group_sync_service_->GetGroup(group_4.saved_guid()),
+            std::nullopt);
+
+  // Verify model internals.
+  EXPECT_FALSE(model_->Contains(group_4.saved_guid()));
+  EXPECT_EQ(model_->Count(), 3);
+}
+
+TEST_F(TabGroupSyncServiceTest, UpdateVisualData) {
+  tab_groups::TabGroupVisualData visual_data = test::CreateTabGroupVisualData();
+  tab_group_sync_service_->UpdateVisualData(local_group_id_1_, &visual_data);
+
+  auto group = tab_group_sync_service_->GetGroup(local_group_id_1_);
+  EXPECT_TRUE(group.has_value());
+
+  EXPECT_EQ(group->saved_guid(), group_1_.saved_guid());
+  EXPECT_EQ(group->title(), visual_data.title());
+  EXPECT_EQ(group->color(), visual_data.color());
+}
+
+TEST_F(TabGroupSyncServiceTest, UpdateLocalTabGroupId) {
+  LocalTabGroupID local_id_2 = test::GenerateRandomTabGroupID();
+  tab_group_sync_service_->UpdateLocalTabGroupId(group_1_.saved_guid(),
+                                                 local_id_2);
+
+  auto retrieved_group = tab_group_sync_service_->GetGroup(local_id_2);
+  EXPECT_TRUE(retrieved_group.has_value());
+
+  EXPECT_EQ(retrieved_group->local_group_id().value(), local_id_2);
+  EXPECT_EQ(retrieved_group->saved_guid(), group_1_.saved_guid());
+  EXPECT_EQ(retrieved_group->title(), group_1_.title());
+  EXPECT_EQ(retrieved_group->color(), group_1_.color());
+
+  test::CompareSavedTabGroupTabs(retrieved_group->saved_tabs(),
+                                 group_1_.saved_tabs());
+}
+
+TEST_F(TabGroupSyncServiceTest, AddTab) {
+  auto local_tab_id_2 = test::GenerateRandomTabID();
+  tab_group_sync_service_->AddTab(local_group_id_1_, local_tab_id_2,
+                                  u"random tab title", GURL("www.google.com"),
+                                  std::nullopt);
+
+  auto group = tab_group_sync_service_->GetGroup(group_1_.saved_guid());
+  EXPECT_TRUE(group.has_value());
+  EXPECT_EQ(2u, group->saved_tabs().size());
+}
+
+TEST_F(TabGroupSyncServiceTest, RemoveTab) {
+  // Add a new tab.
+  auto local_tab_id_2 = test::GenerateRandomTabID();
+  tab_group_sync_service_->AddTab(local_group_id_1_, local_tab_id_2,
+                                  u"random tab title", GURL("www.google.com"),
+                                  std::nullopt);
+
+  auto group = tab_group_sync_service_->GetGroup(group_1_.saved_guid());
+  EXPECT_TRUE(group.has_value());
+  EXPECT_EQ(2u, group->saved_tabs().size());
+
+  // Remove tab.
+  tab_group_sync_service_->RemoveTab(local_group_id_1_, local_tab_id_2);
+  group = tab_group_sync_service_->GetGroup(group_1_.saved_guid());
+  EXPECT_TRUE(group.has_value());
+  EXPECT_EQ(1u, group->saved_tabs().size());
+
+  // Remove the last tab. The group should be removed from the model.
+  tab_group_sync_service_->RemoveTab(local_group_id_1_, local_tab_id_1_);
+  group = tab_group_sync_service_->GetGroup(group_1_.saved_guid());
+  EXPECT_FALSE(group.has_value());
+}
+
+TEST_F(TabGroupSyncServiceTest, UpdateTab) {
+  auto local_tab_id_2 = test::GenerateRandomTabID();
+  tab_group_sync_service_->AddTab(local_group_id_1_, local_tab_id_2,
+                                  u"random tab title", GURL("www.google.com"),
+                                  std::nullopt);
+
+  // Update tab.
+  std::u16string new_title = u"tab title 2";
+  GURL new_url = GURL("www.example.com");
+  tab_group_sync_service_->UpdateTab(local_group_id_1_, local_tab_id_2,
+                                     new_title, new_url, 2);
+  auto group = tab_group_sync_service_->GetGroup(group_1_.saved_guid());
+  EXPECT_TRUE(group.has_value());
+  EXPECT_EQ(2u, group->saved_tabs().size());
+
+  // Verify updated tab.
+  auto* updated_tab = group->GetTab(local_tab_id_2);
+  EXPECT_TRUE(updated_tab);
+  EXPECT_EQ(new_title, updated_tab->title());
+  EXPECT_EQ(new_url, updated_tab->url());
+}
+
+TEST_F(TabGroupSyncServiceTest, UpdateLocalTabId) {
+  auto tab_guid = group_1_.saved_tabs()[0].saved_tab_guid();
+  auto local_tab_id_2 = test::GenerateRandomTabID();
+  tab_group_sync_service_->UpdateLocalTabId(local_group_id_1_, tab_guid,
+                                            local_tab_id_2);
+  auto group = tab_group_sync_service_->GetGroup(local_group_id_1_);
+  EXPECT_TRUE(group.has_value());
+  EXPECT_EQ(1u, group->saved_tabs().size());
+
+  // Verify updated tab.
+  auto* updated_tab = group->GetTab(tab_guid);
+  EXPECT_TRUE(updated_tab);
+  EXPECT_EQ(local_tab_id_2, updated_tab->local_tab_id().value());
+}
+
+TEST_F(TabGroupSyncServiceTest, OnInitialized) {
+  EXPECT_CALL(*observer_, OnInitialized()).Times(1);
+  model_->LoadStoredEntries(std::vector<sync_pb::SavedTabGroupSpecifics>());
+}
+
+TEST_F(TabGroupSyncServiceTest, OnTabGroupAdded) {
+  SavedTabGroup group_4 = test::CreateTestSavedTabGroup();
+  EXPECT_CALL(*observer_, OnTabGroupAdded(UuidEq(group_4.saved_guid()),
+                                          Eq(TriggerSource::REMOTE)))
+      .Times(1);
+  model_->AddedFromSync(group_4);
+}
+
+TEST_F(TabGroupSyncServiceTest, OnTabGroupUpdated) {
+  TabGroupVisualData visual_data = test::CreateTabGroupVisualData();
+  EXPECT_CALL(*observer_, OnTabGroupUpdated(UuidEq(group_1_.saved_guid()),
+                                            Eq(TriggerSource::REMOTE)))
+      .Times(1);
+  model_->UpdatedVisualDataFromSync(group_1_.saved_guid(), &visual_data);
+}
+
+TEST_F(TabGroupSyncServiceTest, OnTabGroupRemoved) {
+  EXPECT_CALL(*observer_, OnTabGroupRemoved(Eq(local_group_id_1_))).Times(1);
+  model_->RemovedFromSync(group_1_.saved_guid());
+
+  // Try removing a group that doesn't exist.
+  EXPECT_CALL(*observer_, OnTabGroupRemoved(_)).Times(0);
+  model_->RemovedFromSync(group_1_.saved_guid());
 }
 
 }  // namespace tab_groups
diff --git a/components/search/ntp_features.cc b/components/search/ntp_features.cc
index 14d120d..d3415b64 100644
--- a/components/search/ntp_features.cc
+++ b/components/search/ntp_features.cc
@@ -258,7 +258,7 @@
 
 // If enabled, outlook calendar module will be shown.
 BASE_FEATURE(kNtpOutlookCalendarModule,
-             "NtpCalendarModule",
+             "NtpOutlookCalendarModule",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
 // If enabled, Google Photos module will be shown.
diff --git a/components/services/app_service/public/cpp/app_storage/app_storage_file_handler_unittest.cc b/components/services/app_service/public/cpp/app_storage/app_storage_file_handler_unittest.cc
index 70d4362..344f497 100644
--- a/components/services/app_service/public/cpp/app_storage/app_storage_file_handler_unittest.cc
+++ b/components/services/app_service/public/cpp/app_storage/app_storage_file_handler_unittest.cc
@@ -114,7 +114,7 @@
     app2->name = kAppName2;
     app2->short_name = kAppShortName;
     app2->publisher_id = "publisher_id";
-    app2->installer_package_id = PackageId(AppType::kWeb, "publisher_id");
+    app2->installer_package_id = PackageId(PackageType::kWeb, "publisher_id");
     app2->description = "description";
     app2->version = "version";
     app2->additional_search_terms = {"item1", "item2"};
diff --git a/components/services/app_service/public/cpp/app_types.cc b/components/services/app_service/public/cpp/app_types.cc
index aafe4660..7a39c97 100644
--- a/components/services/app_service/public/cpp/app_types.cc
+++ b/components/services/app_service/public/cpp/app_types.cc
@@ -22,6 +22,7 @@
                    kExtension,
                    kStandaloneBrowserExtension,
                    kBruschetta)
+APP_ENUM_TO_STRING(PackageType, kUnknown, kArc, kChromeApp, kWeb, kBorealis)
 APP_ENUM_TO_STRING(Readiness,
                    kUnknown,
                    kReady,
@@ -87,6 +88,47 @@
   }
 }
 
+std::optional<AppType> ConvertPackageTypeToAppType(PackageType package_type) {
+  switch (package_type) {
+    case PackageType::kUnknown:
+      return AppType::kUnknown;
+    case PackageType::kArc:
+      return AppType::kArc;
+    case PackageType::kChromeApp:
+      return AppType::kChromeApp;
+    case PackageType::kWeb:
+      return AppType::kWeb;
+    case PackageType::kBorealis:
+      return AppType::kBorealis;
+  }
+}
+
+std::optional<PackageType> ConvertAppTypeToPackageType(AppType app_type) {
+  switch (app_type) {
+    case AppType::kUnknown:
+      return PackageType::kUnknown;
+    case AppType::kArc:
+      return PackageType::kArc;
+    case AppType::kChromeApp:
+      return PackageType::kChromeApp;
+    case AppType::kWeb:
+      return PackageType::kWeb;
+    case AppType::kBorealis:
+      return PackageType::kBorealis;
+    case AppType::kBruschetta:
+    case AppType::kBuiltIn:
+    case AppType::kCrostini:
+    case AppType::kPluginVm:
+    case AppType::kStandaloneBrowser:
+    case AppType::kRemote:
+    case AppType::kSystemWeb:
+    case AppType::kStandaloneBrowserChromeApp:
+    case AppType::kExtension:
+    case AppType::kStandaloneBrowserExtension:
+      return std::nullopt;
+  }
+}
+
 ApplicationInstallReason ConvertInstallReasonToProtoApplicationInstallReason(
     InstallReason install_reason) {
   switch (install_reason) {
diff --git a/components/services/app_service/public/cpp/app_types.h b/components/services/app_service/public/cpp/app_types.h
index c18137d..d6374059 100644
--- a/components/services/app_service/public/cpp/app_types.h
+++ b/components/services/app_service/public/cpp/app_types.h
@@ -37,6 +37,14 @@
      kBruschetta                   // Bruschetta app, see go/bruschetta.
 )
 
+// When updating the enum below, update
+// //components/services/app_service/public/cpp/macros.h
+// macros if necessary.
+//
+// Used by PackageId mapping closely to corresponding values in AppType but
+// can contain other non-app values e.g. app shortcuts.
+ENUM(PackageType, kUnknown, kArc, kChromeApp, kWeb, kBorealis)
+
 // Whether an app is ready to launch, i.e. installed.
 // Note the enumeration is used in UMA histogram so entries should not be
 // re-ordered or removed. New entries should be added at the bottom.
@@ -129,6 +137,12 @@
 ApplicationType ConvertAppTypeToProtoApplicationType(AppType app_type);
 
 COMPONENT_EXPORT(APP_TYPES)
+std::optional<AppType> ConvertPackageTypeToAppType(PackageType package_type);
+
+COMPONENT_EXPORT(APP_TYPES)
+std::optional<PackageType> ConvertAppTypeToPackageType(AppType app_type);
+
+COMPONENT_EXPORT(APP_TYPES)
 ApplicationInstallReason ConvertInstallReasonToProtoApplicationInstallReason(
     InstallReason install_reason);
 
diff --git a/components/services/app_service/public/cpp/package_id.cc b/components/services/app_service/public/cpp/package_id.cc
index 5ce453f..07a7f8a 100644
--- a/components/services/app_service/public/cpp/package_id.cc
+++ b/components/services/app_service/public/cpp/package_id.cc
@@ -21,34 +21,34 @@
 constexpr char kChromeAppPlatformName[] = "chromeapp";
 constexpr char kWebPlatformName[] = "web";
 
-AppType PlatformNameToAppType(std::string_view platform_name) {
+PackageType PlatformNameToPackageType(std::string_view platform_name) {
   if (platform_name == kArcPlatformName) {
-    return AppType::kArc;
+    return PackageType::kArc;
   }
   if (platform_name == kBorealisPlatformName) {
-    return AppType::kBorealis;
+    return PackageType::kBorealis;
   }
   if (platform_name == kWebPlatformName) {
-    return AppType::kWeb;
+    return PackageType::kWeb;
   }
   if (platform_name == kChromeAppPlatformName) {
-    return AppType::kChromeApp;
+    return PackageType::kChromeApp;
   }
 
-  return AppType::kUnknown;
+  return PackageType::kUnknown;
 }
 
-std::string_view AppTypeToPlatformName(AppType app_type) {
-  switch (app_type) {
-    case AppType::kUnknown:
+std::string_view PackageTypeToPlatformName(PackageType package_type) {
+  switch (package_type) {
+    case PackageType::kUnknown:
       return kUnknownName;
-    case AppType::kArc:
+    case PackageType::kArc:
       return kArcPlatformName;
-    case AppType::kBorealis:
+    case PackageType::kBorealis:
       return kBorealisPlatformName;
-    case AppType::kChromeApp:
+    case PackageType::kChromeApp:
       return kChromeAppPlatformName;
-    case AppType::kWeb:
+    case PackageType::kWeb:
       return kWebPlatformName;
     default:
       NOTREACHED();
@@ -58,26 +58,23 @@
 
 }  // namespace
 
-PackageId::PackageId(AppType app_type, std::string_view identifier)
-    : app_type_(app_type), identifier_(identifier) {
-  DCHECK(app_type_ == AppType::kUnknown || app_type_ == AppType::kArc ||
-         app_type_ == AppType::kBorealis || app_type_ == AppType::kChromeApp ||
-         app_type_ == AppType::kWeb);
+PackageId::PackageId(PackageType package_type, std::string_view identifier)
+    : package_type_(package_type), identifier_(identifier) {
   DCHECK(!identifier_.empty());
 }
 
-PackageId::PackageId() : PackageId(AppType::kUnknown, kUnknownName) {}
+PackageId::PackageId() : PackageId(PackageType::kUnknown, kUnknownName) {}
 
 PackageId::PackageId(const PackageId&) = default;
 PackageId& PackageId::operator=(const PackageId&) = default;
 
 bool PackageId::operator<(const PackageId& rhs) const {
-  if (this->app_type_ < rhs.app_type_) {
+  if (this->package_type_ < rhs.package_type_) {
     return true;
-  } else if (this->app_type_ > rhs.app_type_) {
+  } else if (this->package_type_ > rhs.package_type_) {
     return false;
   }
-  // If we're here, it's because app_type_ == rhs.app_type_.
+  // If we're here, it's because package_type_ == rhs.package_type_.
   if (this->identifier_ < rhs.identifier_) {
     return true;
   } else {
@@ -86,12 +83,12 @@
 }
 
 bool PackageId::operator==(const PackageId& rhs) const {
-  return this->app_type_ == rhs.app_type_ &&
+  return this->package_type_ == rhs.package_type_ &&
          this->identifier_ == rhs.identifier_;
 }
 
 bool PackageId::operator!=(const PackageId& rhs) const {
-  return this->app_type_ != rhs.app_type_ ||
+  return this->package_type_ != rhs.package_type_ ||
          this->identifier_ != rhs.identifier_;
 }
 
@@ -104,8 +101,9 @@
     return std::nullopt;
   }
 
-  AppType type = PlatformNameToAppType(package_id_string.substr(0, separator));
-  if (type == AppType::kUnknown) {
+  PackageType type =
+      PlatformNameToPackageType(package_id_string.substr(0, separator));
+  if (type == PackageType::kUnknown) {
     return std::nullopt;
   }
 
@@ -113,7 +111,8 @@
 }
 
 std::string PackageId::ToString() const {
-  return base::StrCat({AppTypeToPlatformName(app_type_), ":", identifier_});
+  return base::StrCat(
+      {PackageTypeToPlatformName(package_type_), ":", identifier_});
 }
 
 std::ostream& operator<<(std::ostream& out, const PackageId& package_id) {
diff --git a/components/services/app_service/public/cpp/package_id.h b/components/services/app_service/public/cpp/package_id.h
index 85598d2..a59bcc4 100644
--- a/components/services/app_service/public/cpp/package_id.h
+++ b/components/services/app_service/public/cpp/package_id.h
@@ -18,27 +18,27 @@
 // globally unique across app platforms, which makes them suitable for
 // identifying apps when communicating with external systems.
 //
-// Package ID is a composite key of "App Type" and a string
+// Package ID is a composite key of "Package Type" and a string
 // "Identifier", which is the platform-specific unique ID for the package.
 // Package IDs have a canonical string format of the form
-// "<app_type_name>:<identifier>".
+// "<package_type_name>:<identifier>".
 //
 // Package IDs only support a subset of app types supported by App Service.
 // Currently, the supported types are:
 //
-// AppType    | Type name   | Identifier value      | Example Package ID string
-// -----------|-------------|-----------------------|--------------------------
-// kArc       | "android"   | package name          | "android:com.foo.bar"
-// kBorealis  | "steam"     | Steam Game ID         | "steam:123456"
-// kChromeApp | "chromeapp" | Extension ID          | "chromeapp:mmfbcljfglbok"
-// kWeb       | "web"       | processed manifest ID | "web:https://app.com/id"
+// PackageType | Type name   | Identifier value      | Example Package ID string
+// ------------|-------------|-----------------------|--------------------------
+// kArc        | "android"   | package name          | "android:com.foo.bar"
+// kBorealis   | "steam"     | Steam Game ID         | "steam:123456"
+// kChromeApp  | "chromeapp" | Extension ID          | "chromeapp:mmfbcljfglbok"
+// kWeb        | "web"       | processed manifest ID | "web:https://app.com/id"
 class COMPONENT_EXPORT(APP_TYPES) PackageId {
  public:
-  // Creates a Package ID from App Type and opaque package identifier.
-  // `app_type` must be a supported type from the table above, and `identifier`
-  // must be a non-empty string.
-  PackageId(AppType app_type, std::string_view identifier);
-  // Creates a PackageId for an unknown app.
+  // Creates a Package ID from PackageType and opaque package identifier.
+  // `package_type` must be a supported type from the table above, and
+  // `identifier` must be a non-empty string.
+  PackageId(PackageType package_type, std::string_view identifier);
+  // Creates a PackageId for an unknown package.
   PackageId();
 
   PackageId(const PackageId&);
@@ -57,9 +57,9 @@
   // Returns the package ID formatted in canonical string form.
   std::string ToString() const;
 
-  // Returns the app type for the package. The type can be AppType::kUnknown if
-  // the PackageId is for an unknown app.
-  AppType app_type() const { return app_type_; }
+  // Returns the package type for the package. The type can be
+  // PackageType::kUnknown if the PackageId is for an unknown package.
+  PackageType package_type() const { return package_type_; }
 
   // Returns the platform-specific identifier for the package (i.e. manifest ID
   // for web apps, package name for ARC apps). The identifier is guaranteed to
@@ -67,7 +67,7 @@
   const std::string& identifier() const { return identifier_; }
 
  private:
-  AppType app_type_;
+  PackageType package_type_;
   std::string identifier_;
 };
 
diff --git a/components/services/app_service/public/cpp/package_id_unittest.cc b/components/services/app_service/public/cpp/package_id_unittest.cc
index 8867f75..f62a77f 100644
--- a/components/services/app_service/public/cpp/package_id_unittest.cc
+++ b/components/services/app_service/public/cpp/package_id_unittest.cc
@@ -16,7 +16,7 @@
       PackageId::FromString("web:https://www.app.com/");
 
   ASSERT_TRUE(id.has_value());
-  ASSERT_EQ(id->app_type(), AppType::kWeb);
+  ASSERT_EQ(id->package_type(), PackageType::kWeb);
   ASSERT_EQ(id->identifier(), "https://www.app.com/");
 }
 
@@ -25,7 +25,7 @@
       PackageId::FromString("android:com.google.android.apps.photos");
 
   ASSERT_TRUE(id.has_value());
-  ASSERT_EQ(id->app_type(), AppType::kArc);
+  ASSERT_EQ(id->package_type(), PackageType::kArc);
   ASSERT_EQ(id->identifier(), "com.google.android.apps.photos");
 }
 
@@ -34,7 +34,7 @@
       PackageId::FromString("chromeapp:mmfbcljfglbokpmkimbfghdkjmjhdgbg");
 
   ASSERT_TRUE(id.has_value());
-  ASSERT_EQ(id->app_type(), AppType::kChromeApp);
+  ASSERT_EQ(id->package_type(), PackageType::kChromeApp);
   ASSERT_EQ(id->identifier(), "mmfbcljfglbokpmkimbfghdkjmjhdgbg");
 }
 
@@ -58,25 +58,25 @@
 }
 
 TEST_F(PackageIdTest, ToStringWeb) {
-  PackageId id(AppType::kWeb, "https://www.app.com/");
+  PackageId id(PackageType::kWeb, "https://www.app.com/");
 
   ASSERT_EQ(id.ToString(), "web:https://www.app.com/");
 }
 
 TEST_F(PackageIdTest, ToStringAndroid) {
-  PackageId id(AppType::kArc, "com.google.android.apps.photos");
+  PackageId id(PackageType::kArc, "com.google.android.apps.photos");
 
   ASSERT_EQ(id.ToString(), "android:com.google.android.apps.photos");
 }
 
 TEST_F(PackageIdTest, ToStringChromeApp) {
-  PackageId id(AppType::kChromeApp, "mmfbcljfglbokpmkimbfghdkjmjhdgbg");
+  PackageId id(PackageType::kChromeApp, "mmfbcljfglbokpmkimbfghdkjmjhdgbg");
 
   ASSERT_EQ(id.ToString(), "chromeapp:mmfbcljfglbokpmkimbfghdkjmjhdgbg");
 }
 
 TEST_F(PackageIdTest, ToStringUnknown) {
-  PackageId id(AppType::kUnknown, "someapp");
+  PackageId id(PackageType::kUnknown, "someapp");
 
   ASSERT_EQ(id.ToString(), "unknown:someapp");
 }
diff --git a/components/services/storage/dom_storage/async_dom_storage_database.cc b/components/services/storage/dom_storage/async_dom_storage_database.cc
index c237b17c6..15ede51 100644
--- a/components/services/storage/dom_storage/async_dom_storage_database.cc
+++ b/components/services/storage/dom_storage/async_dom_storage_database.cc
@@ -58,44 +58,6 @@
 
 AsyncDomStorageDatabase::~AsyncDomStorageDatabase() = default;
 
-void AsyncDomStorageDatabase::Put(const std::vector<uint8_t>& key,
-                                  const std::vector<uint8_t>& value,
-                                  StatusCallback callback) {
-  RunDatabaseTask(
-      base::BindOnce(
-          [](const std::vector<uint8_t>& key, const std::vector<uint8_t>& value,
-             const DomStorageDatabase& db) { return db.Put(key, value); },
-          key, value),
-      std::move(callback));
-}
-
-void AsyncDomStorageDatabase::Delete(const std::vector<uint8_t>& key,
-                                     StatusCallback callback) {
-  RunDatabaseTask(
-      base::BindOnce(
-          [](const std::vector<uint8_t>& key, const DomStorageDatabase& db) {
-            return db.Delete(key);
-          },
-          key),
-      std::move(callback));
-}
-
-void AsyncDomStorageDatabase::DeletePrefixed(
-    const std::vector<uint8_t>& key_prefix,
-    StatusCallback callback) {
-  RunDatabaseTask(
-      base::BindOnce(
-          [](const std::vector<uint8_t>& prefix, const DomStorageDatabase& db) {
-            leveldb::WriteBatch batch;
-            leveldb::Status status = db.DeletePrefixed(prefix, &batch);
-            if (!status.ok())
-              return status;
-            return db.Commit(&batch);
-          },
-          key_prefix),
-      std::move(callback));
-}
-
 void AsyncDomStorageDatabase::RewriteDB(StatusCallback callback) {
   DCHECK(database_);
   database_.PostTaskWithThisObject(base::BindOnce(
@@ -108,27 +70,6 @@
       std::move(callback), base::SequencedTaskRunner::GetCurrentDefault()));
 }
 
-void AsyncDomStorageDatabase::Get(const std::vector<uint8_t>& key,
-                                  GetCallback callback) {
-  struct GetResult {
-    leveldb::Status status;
-    DomStorageDatabase::Value value;
-  };
-  RunDatabaseTask(
-      base::BindOnce(
-          [](const std::vector<uint8_t>& key, const DomStorageDatabase& db) {
-            GetResult result;
-            result.status = db.Get(key, &result.value);
-            return result;
-          },
-          key),
-      base::BindOnce(
-          [](GetCallback callback, GetResult result) {
-            std::move(callback).Run(result.status, result.value);
-          },
-          std::move(callback)));
-}
-
 void AsyncDomStorageDatabase::CopyPrefixed(
     const std::vector<uint8_t>& source_key_prefix,
     const std::vector<uint8_t>& destination_key_prefix,
diff --git a/components/services/storage/dom_storage/async_dom_storage_database.h b/components/services/storage/dom_storage/async_dom_storage_database.h
index 3394da7..483ca6e 100644
--- a/components/services/storage/dom_storage/async_dom_storage_database.h
+++ b/components/services/storage/dom_storage/async_dom_storage_database.h
@@ -57,21 +57,8 @@
     return database_;
   }
 
-  void Put(const std::vector<uint8_t>& key,
-           const std::vector<uint8_t>& value,
-           StatusCallback callback);
-
-  void Delete(const std::vector<uint8_t>& key, StatusCallback callback);
-
-  void DeletePrefixed(const std::vector<uint8_t>& key_prefix,
-                      StatusCallback callback);
-
   void RewriteDB(StatusCallback callback);
 
-  using GetCallback = base::OnceCallback<void(leveldb::Status status,
-                                              const std::vector<uint8_t>&)>;
-  void Get(const std::vector<uint8_t>& key, GetCallback callback);
-
   void CopyPrefixed(const std::vector<uint8_t>& source_key_prefix,
                     const std::vector<uint8_t>& destination_key_prefix,
                     StatusCallback callback);
diff --git a/components/services/storage/dom_storage/dom_storage_database.cc b/components/services/storage/dom_storage/dom_storage_database.cc
index 24a7c46..4bf2d0f 100644
--- a/components/services/storage/dom_storage/dom_storage_database.cc
+++ b/components/services/storage/dom_storage/dom_storage_database.cc
@@ -48,13 +48,6 @@
       .AsUTF8Unsafe();
 }
 
-leveldb_env::Options CreateDefaultInMemoryOptions() {
-  leveldb_env::Options options;
-  options.create_if_missing = true;
-  options.max_open_files = 0;
-  return options;
-}
-
 leveldb_env::Options AddEnvToOptions(const leveldb_env::Options& options,
                                      leveldb::Env* env) {
   leveldb_env::Options new_options = options;
@@ -159,7 +152,7 @@
     StatusCallback callback)
     : DomStorageDatabase("",
                          leveldb_chrome::NewMemEnv(tracking_name),
-                         CreateDefaultInMemoryOptions(),
+                         leveldb_env::Options(),
                          memory_dump_id,
                          std::move(callback_task_runner),
                          std::move(callback)) {}
@@ -307,13 +300,6 @@
   return db_->Put(leveldb::WriteOptions(), MakeSlice(key), MakeSlice(value));
 }
 
-DomStorageDatabase::Status DomStorageDatabase::Delete(KeyView key) const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!db_)
-    return Status::IOError(kInvalidDatabaseMessage);
-  return db_->Delete(leveldb::WriteOptions(), MakeSlice(key));
-}
-
 DomStorageDatabase::Status DomStorageDatabase::GetPrefixed(
     KeyView prefix,
     std::vector<KeyValuePair>* entries) const {
diff --git a/components/services/storage/dom_storage/dom_storage_database.h b/components/services/storage/dom_storage/dom_storage_database.h
index d35e37ce..c461d8ce 100644
--- a/components/services/storage/dom_storage/dom_storage_database.h
+++ b/components/services/storage/dom_storage/dom_storage_database.h
@@ -126,9 +126,6 @@
   // Sets the database entry for |key| to |value|.
   Status Put(KeyView key, ValueView value) const;
 
-  // Deletes the database entry for |key|.
-  Status Delete(KeyView key) const;
-
   // Gets all database entries whose key starts with |prefix|.
   Status GetPrefixed(KeyView prefix, std::vector<KeyValuePair>* entries) const;
 
diff --git a/components/services/storage/dom_storage/dom_storage_database_unittest.cc b/components/services/storage/dom_storage/dom_storage_database_unittest.cc
index 4f9ae0a..a14fdf8 100644
--- a/components/services/storage/dom_storage/dom_storage_database_unittest.cc
+++ b/components/services/storage/dom_storage/dom_storage_database_unittest.cc
@@ -204,14 +204,6 @@
     EXPECT_STATUS_OK(db.Get(MakeBytes(kTestKey), &value));
     EXPECT_VALUE_EQ(kTestValue, value);
   });
-
-  // Now delete the key and expect the following read to fail.
-  DoSync(database, [&](const DomStorageDatabase& db) {
-    EXPECT_STATUS_OK(db.Delete(MakeBytes(kTestKey)));
-
-    DomStorageDatabase::Value value;
-    EXPECT_STATUS(IsNotFound, db.Get(MakeBytes(kTestKey), &value));
-  });
 }
 
 TEST_F(StorageServiceDomStorageDatabaseTest, Reopen) {
diff --git a/components/services/storage/dom_storage/local_storage_impl.cc b/components/services/storage/dom_storage/local_storage_impl.cc
index c4f65dd..ed110bb 100644
--- a/components/services/storage/dom_storage/local_storage_impl.cc
+++ b/components/services/storage/dom_storage/local_storage_impl.cc
@@ -565,9 +565,16 @@
 
   // Verify DB schema version.
   if (database_) {
-    database_->Get(std::vector<uint8_t>(kVersionKey.begin(), kVersionKey.end()),
-                   base::BindOnce(&LocalStorageImpl::OnGotDatabaseVersion,
-                                  weak_ptr_factory_.GetWeakPtr()));
+    database_->RunDatabaseTask(
+        base::BindOnce(
+            [](const std::vector<uint8_t>& key, const DomStorageDatabase& db) {
+              DomStorageDatabase::Value value;
+              leveldb::Status status = db.Get(key, &value);
+              return std::make_tuple(status, std::move(value));
+            },
+            std::vector<uint8_t>(kVersionKey.begin(), kVersionKey.end())),
+        base::BindOnce(&LocalStorageImpl::OnGotDatabaseVersion,
+                       weak_ptr_factory_.GetWeakPtr()));
     return;
   }
 
@@ -575,7 +582,7 @@
 }
 
 void LocalStorageImpl::OnGotDatabaseVersion(leveldb::Status status,
-                                            const std::vector<uint8_t>& value) {
+                                            DomStorageDatabase::Value value) {
   if (status.IsNotFound()) {
     // New database, nothing more to do. Current version will get written
     // when first data is committed.
@@ -583,7 +590,7 @@
     // Existing database, check if version number matches current schema
     // version.
     int64_t db_version;
-    if (!base::StringToInt64(std::string(value.begin(), value.end()),
+    if (!base::StringToInt64(base::as_string_view(base::span(value)),
                              &db_version) ||
         db_version < kMinSchemaVersion ||
         db_version > kCurrentLocalStorageSchemaVersion) {
diff --git a/components/services/storage/dom_storage/local_storage_impl.h b/components/services/storage/dom_storage/local_storage_impl.h
index 39b96594..5cc5b36 100644
--- a/components/services/storage/dom_storage/local_storage_impl.h
+++ b/components/services/storage/dom_storage/local_storage_impl.h
@@ -116,7 +116,7 @@
   void InitiateConnection(bool in_memory_only = false);
   void OnDatabaseOpened(leveldb::Status status);
   void OnGotDatabaseVersion(leveldb::Status status,
-                            const std::vector<uint8_t>& value);
+                            DomStorageDatabase::Value value);
   void OnConnectionFinished();
   void DeleteAndRecreateDatabase();
   void OnDBDestroyed(bool recreate_in_memory, leveldb::Status status);
diff --git a/components/services/storage/dom_storage/session_storage_area_impl_unittest.cc b/components/services/storage/dom_storage/session_storage_area_impl_unittest.cc
index 660a004..8b27b79 100644
--- a/components/services/storage/dom_storage/session_storage_area_impl_unittest.cc
+++ b/components/services/storage/dom_storage/session_storage_area_impl_unittest.cc
@@ -63,8 +63,12 @@
         std::nullopt, "SessionStorageAreaImplTestDatabase",
         base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}),
         base::DoNothing());
-    leveldb_database_->Put(StdStringToUint8Vector("map-0-key1"),
-                           StdStringToUint8Vector("data1"), base::DoNothing());
+    leveldb_database_->RunDatabaseTask(
+        base::BindOnce([](const DomStorageDatabase& db) {
+          return db.Put(StdStringToUint8Vector("map-0-key1"),
+                        StdStringToUint8Vector("data1"));
+        }),
+        base::DoNothing());
 
     std::vector<AsyncDomStorageDatabase::BatchDatabaseTask> save_tasks =
         metadata_.SetupNewDatabase();
diff --git a/components/services/storage/dom_storage/session_storage_impl_unittest.cc b/components/services/storage/dom_storage/session_storage_impl_unittest.cc
index 485c9e02..46540364 100644
--- a/components/services/storage/dom_storage/session_storage_impl_unittest.cc
+++ b/components/services/storage/dom_storage/session_storage_impl_unittest.cc
@@ -971,12 +971,14 @@
 
   // Clear all the data from the backing database.
   base::RunLoop loop;
-  session_storage_impl()->DatabaseForTesting()->DeletePrefixed(
-      StringViewToUint8Vector("map"),
-      base::BindLambdaForTesting([&](leveldb::Status status) {
-        loop.Quit();
-        EXPECT_TRUE(status.ok());
-      }));
+  session_storage_impl()->DatabaseForTesting()->RunDatabaseTask(
+      base::BindOnce([](const DomStorageDatabase& db) {
+        leveldb::WriteBatch batch;
+        db.DeletePrefixed(StringViewToUint8Vector("map"), &batch);
+        EXPECT_TRUE(db.Commit(&batch).ok());
+        return 0;
+      }),
+      base::IgnoreArgs<int>(loop.QuitClosure()));
   loop.Run();
 
   // Now open many new wrappers (for different storage_keys) to trigger clean
diff --git a/components/site_settings_strings.grdp b/components/site_settings_strings.grdp
index d7177e4..ef8be55 100644
--- a/components/site_settings_strings.grdp
+++ b/components/site_settings_strings.grdp
@@ -23,10 +23,10 @@
     automatic downloads
   </message>
   <message name="IDS_SITE_SETTINGS_TYPE_AUTOMATIC_FULLSCREEN" desc="The label used for the automatic fullscreen site settings controls.">
-    Automatic fullscreen
+    Automatic full screen
   </message>
   <message name="IDS_SITE_SETTINGS_TYPE_AUTOMATIC_FULLSCREEN_MID_SENTENCE" desc="The label used for the automatic fullscreen site settings controls when used mid-sentence.">
-    automatic fullscreen
+    automatic full screen
   </message>
   <message name="IDS_SITE_SETTINGS_TYPE_AUTO_PICTURE_IN_PICTURE" desc="The label used for the auto-picture-in-picture site settings controls.">
     Automatic picture-in-picture
diff --git a/components/site_settings_strings_grdp/IDS_SITE_SETTINGS_TYPE_AUTOMATIC_FULLSCREEN.png.sha1 b/components/site_settings_strings_grdp/IDS_SITE_SETTINGS_TYPE_AUTOMATIC_FULLSCREEN.png.sha1
index 8f464869..6a5d4a5 100644
--- a/components/site_settings_strings_grdp/IDS_SITE_SETTINGS_TYPE_AUTOMATIC_FULLSCREEN.png.sha1
+++ b/components/site_settings_strings_grdp/IDS_SITE_SETTINGS_TYPE_AUTOMATIC_FULLSCREEN.png.sha1
@@ -1 +1 @@
-aa3a0053a6d684500b87dd76324711b5381efe6b
\ No newline at end of file
+bb499ae800d7aa2622aa95d65c906d38a3db8e70
\ No newline at end of file
diff --git a/components/site_settings_strings_grdp/IDS_SITE_SETTINGS_TYPE_AUTOMATIC_FULLSCREEN_MID_SENTENCE.png.sha1 b/components/site_settings_strings_grdp/IDS_SITE_SETTINGS_TYPE_AUTOMATIC_FULLSCREEN_MID_SENTENCE.png.sha1
index abd69f7..91f453d 100644
--- a/components/site_settings_strings_grdp/IDS_SITE_SETTINGS_TYPE_AUTOMATIC_FULLSCREEN_MID_SENTENCE.png.sha1
+++ b/components/site_settings_strings_grdp/IDS_SITE_SETTINGS_TYPE_AUTOMATIC_FULLSCREEN_MID_SENTENCE.png.sha1
@@ -1 +1 @@
-82d02dcb4265d7dabd0b1f04c1d2e5a9268d41ac
\ No newline at end of file
+33f403734f5fa7200159c9c5b3a487e235d0d80b
\ No newline at end of file
diff --git a/components/speech/BUILD.gn b/components/speech/BUILD.gn
index 842b24f..2be48280 100644
--- a/components/speech/BUILD.gn
+++ b/components/speech/BUILD.gn
@@ -18,17 +18,6 @@
     "upstream_loader_client.h",
   ]
 
-  if (!is_android) {
-    sources += [
-      "endpointer/endpointer.cc",
-      "endpointer/endpointer.h",
-      "endpointer/energy_endpointer.cc",
-      "endpointer/energy_endpointer.h",
-      "endpointer/energy_endpointer_params.cc",
-      "endpointer/energy_endpointer_params.h",
-    ]
-  }
-
   public_deps = [ "//base" ]
   deps = [
     "//mojo/public/cpp/bindings",
@@ -42,16 +31,7 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [
-    "chunked_byte_buffer_unittest.cc",
-  ]
-
-  if (!is_android) {
-    sources += [
-      "endpointer/endpointer_unittest.cc",
-    ]
-  }
-
+  sources = [ "chunked_byte_buffer_unittest.cc" ]
   deps = [
     ":speech",
     "//testing/gtest",
diff --git a/components/tracing/BUILD.gn b/components/tracing/BUILD.gn
index 0755d1b..f621b3e 100644
--- a/components/tracing/BUILD.gn
+++ b/components/tracing/BUILD.gn
@@ -21,6 +21,10 @@
 
 component("startup_tracing") {
   sources = [
+    "common/background_tracing_state_manager.cc",
+    "common/background_tracing_state_manager.h",
+    "common/pref_names.cc",
+    "common/pref_names.h",
     "common/trace_startup_config.cc",
     "common/trace_startup_config.h",
     "common/trace_to_console.cc",
@@ -43,6 +47,7 @@
 
   deps = [
     "//base",
+    "//components/prefs",
     "//third_party/perfetto:libperfetto",
   ]
 }
@@ -66,20 +71,14 @@
 
 component("background_tracing_utils") {
   sources = [
-    "common/background_tracing_state_manager.cc",
-    "common/background_tracing_state_manager.h",
     "common/background_tracing_utils.cc",
     "common/background_tracing_utils.h",
-    "common/pref_names.cc",
-    "common/pref_names.h",
   ]
 
   defines = [ "IS_BACKGROUND_TRACING_UTILS_IMPL" ]
 
   deps = [
-    ":startup_tracing",
     "//base",
-    "//components/prefs",
     "//content/public/browser",
   ]
 }
diff --git a/components/tracing/common/background_tracing_state_manager.cc b/components/tracing/common/background_tracing_state_manager.cc
index fb65b7e4..f9d9e46a 100644
--- a/components/tracing/common/background_tracing_state_manager.cc
+++ b/components/tracing/common/background_tracing_state_manager.cc
@@ -4,46 +4,58 @@
 
 #include "components/tracing/common/background_tracing_state_manager.h"
 
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+
 #include "base/json/values_util.h"
+#include "base/memory/ptr_util.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/values.h"
 #include "components/tracing/common/pref_names.h"
-#include "content/public/browser/background_tracing_config.h"
-#include "content/public/browser/browser_thread.h"
 
+namespace tracing {
 namespace {
 
 constexpr char kTracingStateKey[] = "state";
+constexpr char kTracingEnabledScenariosKey[] = "enabled_scenarios";
+constexpr char kTracingPrivacyFilterKey[] = "privacy_filter";
+
+BackgroundTracingStateManager* g_background_tracing_state_manager = nullptr;
 
 }  // namespace
 
-namespace tracing {
+BackgroundTracingStateManager::BackgroundTracingStateManager(
+    PrefService* local_state)
+    : local_state_(local_state) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(g_background_tracing_state_manager, nullptr);
+  g_background_tracing_state_manager = this;
 
-BackgroundTracingStateManager::BackgroundTracingStateManager() = default;
-BackgroundTracingStateManager::~BackgroundTracingStateManager() = default;
+  Initialize();
+}
+
+BackgroundTracingStateManager::~BackgroundTracingStateManager() {
+  DCHECK_EQ(g_background_tracing_state_manager, this);
+  g_background_tracing_state_manager = nullptr;
+}
+
+std::unique_ptr<BackgroundTracingStateManager>
+BackgroundTracingStateManager::CreateInstance(PrefService* local_state) {
+  if (local_state == nullptr) {
+    return nullptr;
+  }
+  return base::WrapUnique(new BackgroundTracingStateManager(local_state));
+}
 
 BackgroundTracingStateManager& BackgroundTracingStateManager::GetInstance() {
-  static base::NoDestructor<BackgroundTracingStateManager> instance;
-  return *instance;
+  CHECK_NE(nullptr, g_background_tracing_state_manager);
+  return *g_background_tracing_state_manager;
 }
 
-void BackgroundTracingStateManager::SetPrefServiceForTesting(
-    PrefService* local_state) {
-  if (!local_state_ && local_state)
-    local_state_ = local_state;
-}
-
-void BackgroundTracingStateManager::Initialize(PrefService* local_state) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (initialized_)
-    return;
-
-  initialized_ = true;
-
-  if (!local_state_ && local_state)
-    local_state_ = local_state;
-
+void BackgroundTracingStateManager::Initialize() {
   DCHECK(local_state_);
+
   const base::Value::Dict& dict =
       local_state_->GetDict(kBackgroundTracingSessionState);
 
@@ -58,29 +70,49 @@
     }
   }
 
+  auto* scenarios = dict.FindList(kTracingEnabledScenariosKey);
+  if (scenarios) {
+    for (const auto& item : *scenarios) {
+      auto* scenario_hash = item.GetIfString();
+      if (scenario_hash) {
+        enabled_scenarios_.push_back(*scenario_hash);
+      }
+    }
+  }
+
+  std::optional<bool> privacy_filter_enabled =
+      dict.FindBool(kTracingPrivacyFilterKey);
+  if (privacy_filter_enabled) {
+    privacy_filter_enabled_ = *privacy_filter_enabled;
+  }
+
   // Save state to update the current session state, replacing the previous
   // session state.
   SaveState();
 }
 
 void BackgroundTracingStateManager::SaveState() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(initialized_);
-  SaveState(state_);
-}
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(local_state_);
 
-void BackgroundTracingStateManager::SaveState(BackgroundTracingState state) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   base::Value::Dict dict;
-  dict.Set(kTracingStateKey, static_cast<int>(state));
+  dict.Set(kTracingStateKey, static_cast<int>(state_));
+
+  if (!enabled_scenarios_.empty()) {
+    base::Value::List scenarios;
+    for (const auto& scenario_name : enabled_scenarios_) {
+      scenarios.Append(scenario_name);
+    }
+    dict.Set(kTracingEnabledScenariosKey, std::move(scenarios));
+  }
+  dict.Set(kTracingPrivacyFilterKey, privacy_filter_enabled_);
 
   local_state_->SetDict(kBackgroundTracingSessionState, std::move(dict));
   local_state_->CommitPendingWrite();
 }
 
 bool BackgroundTracingStateManager::DidLastSessionEndUnexpectedly() const {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(initialized_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   switch (last_session_end_state_) {
     case BackgroundTracingState::NOT_ACTIVATED:
     case BackgroundTracingState::RAN_30_SECONDS:
@@ -103,7 +135,7 @@
 }
 
 void BackgroundTracingStateManager::OnTracingStarted() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   SetState(BackgroundTracingState::STARTED);
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, base::BindOnce([]() {
@@ -114,13 +146,23 @@
 }
 
 void BackgroundTracingStateManager::OnTracingStopped() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   SetState(BackgroundTracingState::FINALIZATION_STARTED);
 }
 
+void BackgroundTracingStateManager::UpdateEnabledScenarios(
+    std::vector<std::string> enabled_scenarios) {
+  enabled_scenarios_ = std::move(enabled_scenarios);
+  SaveState();
+}
+
+void BackgroundTracingStateManager::UpdatePrivacyFilter(bool enabled) {
+  privacy_filter_enabled_ = enabled;
+  SaveState();
+}
+
 void BackgroundTracingStateManager::SetState(BackgroundTracingState new_state) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(initialized_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (state_ == new_state) {
     return;
   }
@@ -134,10 +176,11 @@
 }
 
 void BackgroundTracingStateManager::ResetForTesting() {
-  initialized_ = false;
-  local_state_ = nullptr;
   state_ = BackgroundTracingState::NOT_ACTIVATED;
   last_session_end_state_ = BackgroundTracingState::NOT_ACTIVATED;
+  enabled_scenarios_ = {};
+  privacy_filter_enabled_ = true;
+  Initialize();
 }
 
 }  // namespace tracing
diff --git a/components/tracing/common/background_tracing_state_manager.h b/components/tracing/common/background_tracing_state_manager.h
index 03c3442..8a3a7288 100644
--- a/components/tracing/common/background_tracing_state_manager.h
+++ b/components/tracing/common/background_tracing_state_manager.h
@@ -5,13 +5,16 @@
 #ifndef COMPONENTS_TRACING_COMMON_BACKGROUND_TRACING_STATE_MANAGER_H_
 #define COMPONENTS_TRACING_COMMON_BACKGROUND_TRACING_STATE_MANAGER_H_
 
-#include "base/component_export.h"
+#include <cstdint>
+
 #include "base/containers/flat_map.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/no_destructor.h"
+#include "base/sequence_checker.h"
 #include "base/time/time.h"
 #include "components/prefs/pref_service.h"
+#include "components/tracing/tracing_export.h"
 
 namespace tracing {
 
@@ -30,18 +33,12 @@
 // Manages local state prefs for background tracing, and tracks state from
 // previous background tracing session(s). All the calls are expected to run on
 // UI thread.
-class COMPONENT_EXPORT(BACKGROUND_TRACING_UTILS) BackgroundTracingStateManager {
+class TRACING_EXPORT BackgroundTracingStateManager {
  public:
+  static std::unique_ptr<BackgroundTracingStateManager> CreateInstance(
+      PrefService* local_state);
   static BackgroundTracingStateManager& GetInstance();
-
-  // Initializes state from previous session and writes current state to
-  // prefs, when called the first time. NOOP on any calls after that. It also
-  // deletes any expired entries from prefs.
-  void Initialize(PrefService* local_state);
-
-  // Used in tests when other methods of this class need to be called before
-  // Initialize().
-  void SetPrefServiceForTesting(PrefService* local_state);
+  ~BackgroundTracingStateManager();
 
   // True if last session potentially crashed and it is unsafe to turn on
   // background tracing in current session.
@@ -52,22 +49,27 @@
   // sequence to update the state once more to denote no crashes after a
   // reasonable time (see DidLastSessionEndUnexpectedly()).
   void OnTracingStarted();
-
   void OnTracingStopped();
 
-  // Saves the given state to prefs, public for testing.
-  void SaveState(BackgroundTracingState state);
+  // Saves user-controlled prefs related to tracing.
+  // `enabled_scenario_hashes` is a list of hashes uniquely identifying scenario
+  // configs.
+  void UpdateEnabledScenarios(std::vector<std::string> enabled_scenario_hashes);
+  void UpdatePrivacyFilter(bool enabled);
+
+  const std::vector<std::string>& enabled_scenarios() const {
+    return enabled_scenarios_;
+  }
+  bool privacy_filter_enabled() const { return privacy_filter_enabled_; }
 
   // Used in tests to reset the state since a singleton instance is never
   // destroyed.
   void ResetForTesting();
 
  private:
-  friend base::NoDestructor<BackgroundTracingStateManager>;
+  explicit BackgroundTracingStateManager(PrefService* local_state);
 
-  BackgroundTracingStateManager();
-  ~BackgroundTracingStateManager();
-
+  void Initialize();
   void SaveState();
 
   // Updates the current tracing state and saves it to prefs.
@@ -75,13 +77,14 @@
 
   BackgroundTracingState state_ = BackgroundTracingState::NOT_ACTIVATED;
 
-  bool initialized_ = false;
-
   raw_ptr<PrefService> local_state_ = nullptr;
+  std::vector<std::string> enabled_scenarios_;
+  bool privacy_filter_enabled_ = true;
 
-  // Following are valid only when |initialized_| = true.
   BackgroundTracingState last_session_end_state_ =
       BackgroundTracingState::NOT_ACTIVATED;
+
+  SEQUENCE_CHECKER(sequence_checker_);
 };
 
 }  // namespace tracing
diff --git a/components/tracing/common/background_tracing_state_manager_unittest.cc b/components/tracing/common/background_tracing_state_manager_unittest.cc
index 98be6e6..3eb43df8 100644
--- a/components/tracing/common/background_tracing_state_manager_unittest.cc
+++ b/components/tracing/common/background_tracing_state_manager_unittest.cc
@@ -25,12 +25,8 @@
         metrics::prefs::kMetricsReportingEnabled, false);
     pref_service_->SetBoolean(metrics::prefs::kMetricsReportingEnabled, true);
     tracing::RegisterPrefs(pref_service_->registry());
-    tracing::BackgroundTracingStateManager::GetInstance()
-        .SetPrefServiceForTesting(pref_service_.get());
-  }
-
-  void TearDown() override {
-    tracing::BackgroundTracingStateManager::GetInstance().ResetForTesting();
+    state_manager_ = tracing::BackgroundTracingStateManager::CreateInstance(
+        pref_service_.get());
   }
 
   std::string GetSessionStateJson() {
@@ -42,88 +38,115 @@
     return json;
   }
 
-  std::unique_ptr<TestingPrefServiceSimple> pref_service_;
+  void SetSessionState(base::Value::Dict dict) {
+    pref_service_->Set(tracing::kBackgroundTracingSessionState,
+                       base::Value(std::move(dict)));
+  }
+
+  void ResetStateManager() {
+    state_manager_.reset();
+    state_manager_ = tracing::BackgroundTracingStateManager::CreateInstance(
+        pref_service_.get());
+  }
 
  private:
   content::BrowserTaskEnvironment task_environment_;
+  std::unique_ptr<TestingPrefServiceSimple> pref_service_;
+  std::unique_ptr<tracing::BackgroundTracingStateManager> state_manager_;
 };
 
 TEST_F(BackgroundTracingStateManagerTest, InitializeEmptyPrefs) {
-  tracing::BackgroundTracingStateManager::GetInstance().Initialize(nullptr);
-  EXPECT_EQ(GetSessionStateJson(), R"({"state":0})");
+  EXPECT_EQ(GetSessionStateJson(), R"({"privacy_filter":true,"state":0})");
 }
 
 TEST_F(BackgroundTracingStateManagerTest, InitializeInvalidState) {
   base::Value::Dict dict;
   dict.Set("state",
            static_cast<int>(tracing::BackgroundTracingState::LAST) + 1);
-  pref_service_->Set(tracing::kBackgroundTracingSessionState,
-                     base::Value(std::move(dict)));
+  SetSessionState(std::move(dict));
+  ResetStateManager();
 
-  tracing::BackgroundTracingStateManager::GetInstance().Initialize(nullptr);
-  EXPECT_EQ(GetSessionStateJson(), R"({"state":0})");
-}
-
-TEST_F(BackgroundTracingStateManagerTest, InitializeNoScenario) {
-  base::Value::Dict dict;
-  dict.Set("state",
-           static_cast<int>(tracing::BackgroundTracingState::NOT_ACTIVATED));
-  base::Value::Dict scenario;
-  scenario.Set("time", base::TimeToValue(base::Time::Now()));
-  pref_service_->Set(tracing::kBackgroundTracingSessionState,
-                     base::Value(std::move(dict)));
-
-  tracing::BackgroundTracingStateManager::GetInstance().Initialize(nullptr);
-  EXPECT_EQ(GetSessionStateJson(), R"({"state":0})");
-}
-
-TEST_F(BackgroundTracingStateManagerTest, InitializeValidPrefs) {
-  base::Value::Dict dict;
-  dict.Set("state",
-           static_cast<int>(tracing::BackgroundTracingState::NOT_ACTIVATED));
-  base::Value::Dict scenario;
-  scenario.Set("scenario", "TestScenario");
-  scenario.Set("time", base::TimeToValue(base::Time::Now()));
-  pref_service_->Set(tracing::kBackgroundTracingSessionState,
-                     base::Value(std::move(dict)));
-
-  tracing::BackgroundTracingStateManager::GetInstance().Initialize(nullptr);
-  EXPECT_TRUE(base::MatchPattern(GetSessionStateJson(), R"({"state":0})"))
-      << "Actual: " << GetSessionStateJson();
-  ;
-}
-
-TEST_F(BackgroundTracingStateManagerTest, SaveStateValidPrefs) {
-  tracing::BackgroundTracingStateManager::GetInstance().SaveState(
-      tracing::BackgroundTracingState::NOT_ACTIVATED);
-  tracing::BackgroundTracingStateManager::GetInstance().Initialize(nullptr);
-
-  EXPECT_TRUE(base::MatchPattern(GetSessionStateJson(), R"({"state":0})"))
-      << "Actual: " << GetSessionStateJson();
-  EXPECT_FALSE(tracing::BackgroundTracingStateManager::GetInstance()
-                   .DidLastSessionEndUnexpectedly());
+  EXPECT_EQ(GetSessionStateJson(), R"({"privacy_filter":true,"state":0})");
 }
 
 TEST_F(BackgroundTracingStateManagerTest, SessionEndedUnexpectedly) {
-  tracing::BackgroundTracingStateManager::GetInstance().SaveState(
-      tracing::BackgroundTracingState::STARTED);
-  tracing::BackgroundTracingStateManager::GetInstance().Initialize(nullptr);
+  base::Value::Dict dict;
+  dict.Set("state", static_cast<int>(tracing::BackgroundTracingState::STARTED));
+  SetSessionState(std::move(dict));
+  ResetStateManager();
+
+  EXPECT_EQ(GetSessionStateJson(), R"({"privacy_filter":true,"state":0})");
   EXPECT_TRUE(tracing::BackgroundTracingStateManager::GetInstance()
                   .DidLastSessionEndUnexpectedly());
 }
 
 TEST_F(BackgroundTracingStateManagerTest, OnTracingStarted) {
-  tracing::BackgroundTracingStateManager::GetInstance().Initialize(nullptr);
   tracing::BackgroundTracingStateManager::GetInstance().OnTracingStarted();
-  EXPECT_TRUE(base::MatchPattern(GetSessionStateJson(), R"({"state":1})"))
+  EXPECT_TRUE(base::MatchPattern(GetSessionStateJson(),
+                                 R"({"privacy_filter":true,"state":1})"))
       << "Actual: " << GetSessionStateJson();
 }
 
 TEST_F(BackgroundTracingStateManagerTest, OnTracingStopped) {
-  tracing::BackgroundTracingStateManager::GetInstance().Initialize(nullptr);
   tracing::BackgroundTracingStateManager::GetInstance().OnTracingStopped();
-  EXPECT_TRUE(base::MatchPattern(GetSessionStateJson(), R"({"state":3})"))
+  EXPECT_TRUE(base::MatchPattern(GetSessionStateJson(),
+                                 R"({"privacy_filter":true,"state":3})"))
       << "Actual: " << GetSessionStateJson();
 }
 
+TEST_F(BackgroundTracingStateManagerTest, DisablePrivacyFilter) {
+  EXPECT_TRUE(tracing::BackgroundTracingStateManager::GetInstance()
+                  .privacy_filter_enabled());
+
+  tracing::BackgroundTracingStateManager::GetInstance().UpdatePrivacyFilter(
+      false);
+
+  EXPECT_TRUE(base::MatchPattern(GetSessionStateJson(),
+                                 R"({"privacy_filter":false,"state":0})"))
+      << "Actual: " << GetSessionStateJson();
+  EXPECT_FALSE(tracing::BackgroundTracingStateManager::GetInstance()
+                   .privacy_filter_enabled());
+}
+
+TEST_F(BackgroundTracingStateManagerTest, PrivacyFilterDisabled) {
+  base::Value::Dict dict;
+  dict.Set("privacy_filter", false);
+  SetSessionState(std::move(dict));
+  ResetStateManager();
+
+  EXPECT_TRUE(base::MatchPattern(GetSessionStateJson(),
+                                 R"({"privacy_filter":false,"state":0})"))
+      << "Actual: " << GetSessionStateJson();
+  EXPECT_FALSE(tracing::BackgroundTracingStateManager::GetInstance()
+                   .privacy_filter_enabled());
+}
+
+TEST_F(BackgroundTracingStateManagerTest, LoadEnabledScenarios) {
+  base::Value::Dict dict;
+  dict.Set("enabled_scenarios", base::Value::List().Append("1").Append("3"));
+  SetSessionState(std::move(dict));
+  ResetStateManager();
+
+  EXPECT_TRUE(base::MatchPattern(
+      GetSessionStateJson(),
+      R"({"enabled_scenarios":["1","3"],"privacy_filter":true,"state":0})"))
+      << "Actual: " << GetSessionStateJson();
+  EXPECT_EQ(std::vector<std::string>({"1", "3"}),
+            tracing::BackgroundTracingStateManager::GetInstance()
+                .enabled_scenarios());
+}
+
+TEST_F(BackgroundTracingStateManagerTest, UpdateEnabledScenarios) {
+  tracing::BackgroundTracingStateManager::GetInstance().UpdateEnabledScenarios(
+      {"1", "3"});
+
+  EXPECT_TRUE(base::MatchPattern(
+      GetSessionStateJson(),
+      R"({"enabled_scenarios":["1","3"],"privacy_filter":true,"state":0})"))
+      << "Actual: " << GetSessionStateJson();
+  EXPECT_EQ(std::vector<std::string>({"1", "3"}),
+            tracing::BackgroundTracingStateManager::GetInstance()
+                .enabled_scenarios());
+}
+
 }  // namespace tracing
diff --git a/components/tracing/common/pref_names.h b/components/tracing/common/pref_names.h
index 8a6c07a..50ab8f8 100644
--- a/components/tracing/common/pref_names.h
+++ b/components/tracing/common/pref_names.h
@@ -5,16 +5,16 @@
 #ifndef COMPONENTS_TRACING_COMMON_PREF_NAMES_H_
 #define COMPONENTS_TRACING_COMMON_PREF_NAMES_H_
 
-#include "base/component_export.h"
+#include "components/tracing/tracing_export.h"
 
 class PrefRegistrySimple;
 
 namespace tracing {
 
-COMPONENT_EXPORT(BACKGROUND_TRACING_UTILS)
+TRACING_EXPORT
 extern const char kBackgroundTracingSessionState[];
 
-COMPONENT_EXPORT(BACKGROUND_TRACING_UTILS)
+TRACING_EXPORT
 void RegisterPrefs(PrefRegistrySimple* registry);
 
 }  // namespace tracing
diff --git a/components/translate/content/renderer/translate_agent.cc b/components/translate/content/renderer/translate_agent.cc
index 3d20dac..ce926fc7 100644
--- a/components/translate/content/renderer/translate_agent.cc
+++ b/components/translate/content/renderer/translate_agent.cc
@@ -493,7 +493,7 @@
 
 void TranslateAgent::RevertTranslation() {
   if (!IsTranslateLibAvailable()) {
-    NOTREACHED();
+    DUMP_WILL_BE_NOTREACHED_NORETURN();
     return;
   }
 
diff --git a/components/update_client/component.cc b/components/update_client/component.cc
index b52e7b31..17ee630 100644
--- a/components/update_client/component.cc
+++ b/components/update_client/component.cc
@@ -562,6 +562,9 @@
   hash_sha256_ = package.hash_sha256;
   hashdiff_sha256_ = package.hashdiff_sha256;
 
+  size_ = package.size;
+  sizediff_ = package.sizediff;
+
   if (!result.manifest.run.empty()) {
     install_params_ = std::make_optional(CrxInstaller::InstallParams(
         result.manifest.run, result.manifest.arguments,
@@ -616,11 +619,18 @@
   NotifyObservers(Events::COMPONENT_WAIT);
 }
 
-bool Component::CanDoBackgroundDownload() const {
+bool Component::CanDoBackgroundDownload(int64_t size) const {
   // Foreground component updates are always downloaded in foreground.
-  return !is_foreground() &&
-         (crx_component() && crx_component()->allows_background_download) &&
-         update_context_->config->EnabledBackgroundDownloader();
+  bool enabled =
+      !is_foreground() &&
+      (crx_component() && crx_component()->allows_background_download) &&
+      update_context_->config->EnabledBackgroundDownloader();
+#if BUILDFLAG(IS_MAC)
+  enabled &=
+      base::FeatureList::IsEnabled(features::kDynamicCrxDownloaderPriority) &&
+      size > features::kDynamicCrxDownloaderPrioritySizeThreshold.Get();
+#endif
+  return enabled;
 }
 
 void Component::AppendEvent(base::Value::Dict event) {
@@ -1031,7 +1041,7 @@
 
   crx_downloader_ =
       component.config()->GetCrxDownloaderFactory()->MakeCrxDownloader(
-          component.CanDoBackgroundDownload());
+          component.CanDoBackgroundDownload(component.sizediff_));
   crx_downloader_->set_progress_callback(
       base::BindRepeating(&Component::StateDownloadingDiff::DownloadProgress,
                           base::Unretained(this)));
@@ -1105,7 +1115,7 @@
 
   crx_downloader_ =
       component.config()->GetCrxDownloaderFactory()->MakeCrxDownloader(
-          component.CanDoBackgroundDownload());
+          component.CanDoBackgroundDownload(component.size_));
   crx_downloader_->set_progress_callback(base::BindRepeating(
       &Component::StateDownloading::DownloadProgress, base::Unretained(this)));
   cancel_callback_ = crx_downloader_->StartDownload(
diff --git a/components/update_client/component.h b/components/update_client/component.h
index c5f79f7..eb1add9 100644
--- a/components/update_client/component.h
+++ b/components/update_client/component.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_UPDATE_CLIENT_COMPONENT_H_
 #define COMPONENTS_UPDATE_CLIENT_COMPONENT_H_
 
+#include <cstdint>
 #include <map>
 #include <memory>
 #include <optional>
@@ -378,7 +379,8 @@
 
   // Returns true is the update payload for this component can be downloaded
   // by a downloader which can do bandwidth throttling on the client side.
-  bool CanDoBackgroundDownload() const;
+  // The decision may be predicated on the expected size of the download.
+  bool CanDoBackgroundDownload(int64_t size) const;
 
   void AppendEvent(base::Value::Dict event);
 
@@ -427,6 +429,10 @@
   std::string hash_sha256_;
   std::string hashdiff_sha256_;
 
+  // The expected size of the download as reported by the update server.
+  int64_t size_ = -1;
+  int64_t sizediff_ = -1;
+
   // The from/to version and fingerprint values.
   base::Version previous_version_;
   base::Version next_version_;
diff --git a/components/update_client/crx_downloader_factory.cc b/components/update_client/crx_downloader_factory.cc
index 401f07a..9353e53 100644
--- a/components/update_client/crx_downloader_factory.cc
+++ b/components/update_client/crx_downloader_factory.cc
@@ -4,6 +4,7 @@
 
 #include "components/update_client/crx_downloader_factory.h"
 
+#include <cstdint>
 #include <optional>
 
 #include "base/files/file_path.h"
@@ -64,15 +65,11 @@
   scoped_refptr<CrxDownloader> url_fetcher_downloader =
       base::MakeRefCounted<UrlFetcherDownloader>(nullptr,
                                                  network_fetcher_factory_);
-
   if (background_download_enabled) {
 #if BUILDFLAG(IS_MAC)
-    if (background_downloader_shared_session_ &&
-        base::FeatureList::IsEnabled(features::kBackgroundCrxDownloaderMac)) {
-      return base::MakeRefCounted<BackgroundDownloader>(
-          url_fetcher_downloader, background_downloader_shared_session_,
-          background_sequence_);
-    }
+    return base::MakeRefCounted<BackgroundDownloader>(
+        url_fetcher_downloader, background_downloader_shared_session_,
+        background_sequence_);
 #elif BUILDFLAG(IS_WIN)
     return base::MakeRefCounted<BackgroundDownloader>(url_fetcher_downloader);
 #endif
diff --git a/components/update_client/crx_downloader_factory.h b/components/update_client/crx_downloader_factory.h
index a35f5a0..a8d022da 100644
--- a/components/update_client/crx_downloader_factory.h
+++ b/components/update_client/crx_downloader_factory.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_UPDATE_CLIENT_CRX_DOWNLOADER_FACTORY_H_
 #define COMPONENTS_UPDATE_CLIENT_CRX_DOWNLOADER_FACTORY_H_
 
+#include <cstdint>
 #include <optional>
 
 #include "base/files/file_path.h"
diff --git a/components/update_client/features.cc b/components/update_client/features.cc
index 7f34186..c1ae0f6 100644
--- a/components/update_client/features.cc
+++ b/components/update_client/features.cc
@@ -11,8 +11,12 @@
 BASE_FEATURE(kPuffinPatches, "PuffinPatches", base::FEATURE_ENABLED_BY_DEFAULT);
 
 #if BUILDFLAG(IS_MAC)
-BASE_FEATURE(kBackgroundCrxDownloaderMac,
-             "BackgroundCrxDownloaderMac",
+BASE_FEATURE(kDynamicCrxDownloaderPriority,
+             "DynamicCrxDownloaderPriority",
              base::FEATURE_DISABLED_BY_DEFAULT);
+
+const base::FeatureParam<int> kDynamicCrxDownloaderPrioritySizeThreshold{
+    &kDynamicCrxDownloaderPriority, "BackgroundCrxDownloaderSizeThreshold",
+    10000000 /*10 MB*/};
 #endif
 }  // namespace update_client::features
diff --git a/components/update_client/features.h b/components/update_client/features.h
index 6691966..2fca64b 100644
--- a/components/update_client/features.h
+++ b/components/update_client/features.h
@@ -9,11 +9,18 @@
 #include "base/feature_list.h"
 #include "build/build_config.h"
 
+#if BUILDFLAG(IS_MAC)
+#include "base/metrics/field_trial_params.h"
+#endif
+
 namespace update_client::features {
 BASE_DECLARE_FEATURE(kPuffinPatches);
 
 #if BUILDFLAG(IS_MAC)
-BASE_DECLARE_FEATURE(kBackgroundCrxDownloaderMac);
+BASE_DECLARE_FEATURE(kDynamicCrxDownloaderPriority);
+// The minimum size (in bytes) for which background downloads should be
+// attempted.
+extern const base::FeatureParam<int> kDynamicCrxDownloaderPrioritySizeThreshold;
 #endif
 }  // namespace update_client::features
 
diff --git a/components/update_client/update_client_unittest.cc b/components/update_client/update_client_unittest.cc
index e849cbcf8..1663834 100644
--- a/components/update_client/update_client_unittest.cc
+++ b/components/update_client/update_client_unittest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <cstdint>
 #include <memory>
 #include <optional>
 #include <string>
diff --git a/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc b/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc
index 9611f66..4505912aa 100644
--- a/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc
+++ b/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc
@@ -182,6 +182,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override {
     auto result = std::make_unique<gpu::TestImageBacking>(
         mailbox, format, size, color_space, surface_origin, alpha_type, usage,
diff --git a/components/webapps/browser/installable/installable_evaluator_unittest.cc b/components/webapps/browser/installable/installable_evaluator_unittest.cc
index f18a799..3ef3ab6 100644
--- a/components/webapps/browser/installable/installable_evaluator_unittest.cc
+++ b/components/webapps/browser/installable/installable_evaluator_unittest.cc
@@ -89,6 +89,7 @@
     manifest->start_url = document_url;
     manifest->scope = document_url.GetWithoutFilename();
     manifest->id = document_url.GetWithoutRef();
+    page_data_->manifest_->fetched = false;
     page_data_->OnManifestFetched(std::move(manifest), /*manifest_url=*/GURL(),
                                   InstallableStatusCode::NO_ERROR_DETECTED);
   }
@@ -174,17 +175,17 @@
 
 TEST_P(InstallableEvaluatorCriteriaUnitTest, UnsetManifest) {
   web_contents_tester()->NavigateAndCommit(GURL("https://www.example.com"));
+  SetManifestAsDefault(GURL("https://www.example.com"));
   TestCheckInstallability(
-      InstallableStatusCode::MANIFEST_PARSING_OR_NETWORK_ERROR,
-      InstallableStatusCode::MANIFEST_PARSING_OR_NETWORK_ERROR,
+      InstallableStatusCode::NO_MANIFEST, InstallableStatusCode::NO_MANIFEST,
       InstallableStatusCode::MANIFEST_MISSING_NAME_OR_SHORT_NAME);
 
   web_contents_tester()->NavigateAndCommit(
       GURL("https://www.example.com/path/page.html"));
-  TestCheckInstallability(
-      InstallableStatusCode::MANIFEST_PARSING_OR_NETWORK_ERROR,
-      InstallableStatusCode::MANIFEST_PARSING_OR_NETWORK_ERROR,
-      InstallableStatusCode::MANIFEST_PARSING_OR_NETWORK_ERROR);
+  SetManifestAsDefault(GURL("https://www.example.com/path/page.html"));
+  TestCheckInstallability(InstallableStatusCode::NO_MANIFEST,
+                          InstallableStatusCode::NO_MANIFEST,
+                          InstallableStatusCode::NO_MANIFEST);
 }
 
 TEST_P(InstallableEvaluatorCriteriaUnitTest, ManifestParsingOrNetworkError) {
@@ -658,6 +659,7 @@
   // Test that a root-scoped page, with no manifest and a valid metadata is
   // installable.
   web_contents_tester()->NavigateAndCommit(GURL("https://www.example.com"));
+  SetManifestAsDefault(GURL("https://www.example.com"));
   SetMetadata(GetWebPageMetadata());
   AddFavicon();
 
diff --git a/components/webxr/android/java/src/org/chromium/components/webxr/XrHostActivity.java b/components/webxr/android/java/src/org/chromium/components/webxr/XrHostActivity.java
index 170cdac..d822b76 100644
--- a/components/webxr/android/java/src/org/chromium/components/webxr/XrHostActivity.java
+++ b/components/webxr/android/java/src/org/chromium/components/webxr/XrHostActivity.java
@@ -9,6 +9,10 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.SurfaceView;
+import android.view.View;
+
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.base.WindowAndroid;
 
 // TODO(https://crbug.com/1435548): Investigate if this would benefit from
 // extending ChromeBaseAppCompatActivity
@@ -19,6 +23,29 @@
  * makes lifetime tracking and returning to the 2D browser when done cleaner.
  */
 public class XrHostActivity extends Activity {
+
+    private static class XrActivityWindow extends WindowAndroid {
+        public XrActivityWindow(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onActivityStarted() {
+            super.onActivityStarted();
+        }
+
+        @Override
+        public void onActivityStopped() {
+            super.onActivityStopped();
+        }
+    }
+
+    private XrActivityWindow mWindow;
+
+    private WindowAndroid mOriginalWindow;
+
+    private WebContents mWebContents;
+
     /**
      * Creates an Intent to start the {@link XrHostActivity}.
      * @param context  Context to use when constructing the Intent.
@@ -44,6 +71,14 @@
 
         SurfaceView defaultView = new SurfaceView(this);
         setContentView(defaultView);
+
+        mWebContents = XrSessionCoordinator.getWebContents();
+        assert mWebContents != null;
+
+        mOriginalWindow = mWebContents.getTopLevelNativeWindow();
+        mWindow = new XrActivityWindow(this);
+        mWindow.setAnimationPlaceholderView(defaultView);
+        mWebContents.setTopLevelNativeWindow(mWindow);
     }
 
     @Override
@@ -55,10 +90,17 @@
     }
 
     @Override
+    public void onStart() {
+        super.onStart();
+
+        mWindow.onActivityStarted();
+    }
+
+    @Override
     public void onStop() {
         super.onStop();
 
-        XrSessionCoordinator.endActiveSessionFromXrHost();
+        onXrSessionEnded();
 
         finishAndRemoveTask();
     }
@@ -67,6 +109,28 @@
     public void onBackPressed() {
         super.onBackPressed();
 
+        onXrSessionEnded();
+    }
+
+    public void onXrSessionEnded() {
         XrSessionCoordinator.endActiveSessionFromXrHost();
+
+        if (mOriginalWindow == null) {
+            return;
+        }
+
+        mWindow.onVisibilityChanged(false);
+        mWindow.onActivityStopped();
+
+        mWebContents.setTopLevelNativeWindow(mOriginalWindow);
+
+        // Need this because original visibility change event
+        // can be happen before window swap.
+        Activity originalActivity = mOriginalWindow.getActivity().get();
+        mOriginalWindow.onVisibilityChanged(
+            originalActivity.getWindow().getDecorView()
+                .getWindowVisibility() == View.VISIBLE);
+
+        mOriginalWindow = null;
     }
 }
diff --git a/components/webxr/android/java/src/org/chromium/components/webxr/XrSessionCoordinator.java b/components/webxr/android/java/src/org/chromium/components/webxr/XrSessionCoordinator.java
index 22943a6..f4dfbc9 100644
--- a/components/webxr/android/java/src/org/chromium/components/webxr/XrSessionCoordinator.java
+++ b/components/webxr/android/java/src/org/chromium/components/webxr/XrSessionCoordinator.java
@@ -83,6 +83,14 @@
         return window.getActivity().get();
     }
 
+    public static WebContents getWebContents() {
+        if (sActiveSessionInstance == null) {
+            return null;
+        }
+
+        return sActiveSessionInstance.mWebContents;
+    }
+
     @CalledByNative
     private static XrSessionCoordinator create(long nativeXrSessionCoordinator) {
         ThreadUtils.assertOnUiThread();
@@ -174,7 +182,7 @@
     }
 
     @CalledByNative
-    private void startXrSession() {
+    private void startXrSession(final WebContents webContents) {
         if (DEBUG_LOGS) Log.i(TAG, "startXrSession");
         // The higher levels should have guaranteed that we're only called if there isn't any other
         // active session going on.
@@ -182,6 +190,7 @@
 
         // The active session must be set before creating the host activity, since it will be
         // notified once the activity is ready.
+        mWebContents = webContents;
         sActiveSessionInstance = this;
         mActiveSessionType = SessionType.VR;
         sActiveSessionAvailableSupplier.set(SessionType.VR);
@@ -324,7 +333,7 @@
      *
      * @return True if an active session was notified that the activity is ready.
      */
-    public static boolean onXrHostActivityReady(Activity activity) {
+    public static boolean onXrHostActivityReady(XrHostActivity activity) {
         if (DEBUG_LOGS) Log.i(TAG, "onXrHostActivityReady");
         if (sActiveSessionInstance != null) {
             sActiveSessionInstance.handleXrHostActivityReady(activity);
@@ -333,7 +342,7 @@
         return false;
     }
 
-    private void handleXrHostActivityReady(Activity activity) {
+    private void handleXrHostActivityReady(XrHostActivity activity) {
         if (mNativeXrSessionCoordinator == 0) return;
         mXrHostActivity = new WeakReference(activity);
         XrSessionCoordinatorJni.get()
diff --git a/components/webxr/android/openxr_platform_helper_android.cc b/components/webxr/android/openxr_platform_helper_android.cc
index d54228b4..67c25dc 100644
--- a/components/webxr/android/openxr_platform_helper_android.cc
+++ b/components/webxr/android/openxr_platform_helper_android.cc
@@ -6,6 +6,7 @@
 #include <vector>
 
 #include "base/android/jni_android.h"
+#include "content/public/browser/web_contents.h"
 #include "components/webxr/android/webxr_utils.h"
 #include "components/webxr/android/xr_session_coordinator.h"
 #include "content/public/browser/web_contents.h"
@@ -37,8 +38,11 @@
   auto activity_ready_callback =
       base::BindOnce(&OpenXrPlatformHelperAndroid::OnXrActivityReady,
                      base::Unretained(this), std::move(result_callback));
-  session_coordinator_->RequestXrSession(std::move(activity_ready_callback),
-                                         std::move(shutdown_callback));
+  session_coordinator_->RequestXrSession(
+      create_info.render_process_id,
+      create_info.render_frame_id,
+      std::move(activity_ready_callback),
+      std::move(shutdown_callback));
 }
 
 void OpenXrPlatformHelperAndroid::OnXrActivityReady(
diff --git a/components/webxr/android/xr_session_coordinator.cc b/components/webxr/android/xr_session_coordinator.cc
index 50577d058..6b422eb2 100644
--- a/components/webxr/android/xr_session_coordinator.cc
+++ b/components/webxr/android/xr_session_coordinator.cc
@@ -91,6 +91,8 @@
 }
 
 void XrSessionCoordinator::RequestXrSession(
+    int render_process_id,
+    int render_frame_id,
     ActivityReadyCallback ready_callback,
     device::JavaShutdownCallback shutdown_callback) {
   DVLOG(1) << __func__;
@@ -99,7 +101,9 @@
   activity_ready_callback_ = std::move(ready_callback);
   java_shutdown_callback_ = std::move(shutdown_callback);
 
-  Java_XrSessionCoordinator_startXrSession(env, j_xr_session_coordinator_);
+  Java_XrSessionCoordinator_startXrSession(
+      env, j_xr_session_coordinator_,
+      webxr::GetJavaWebContents(render_process_id, render_frame_id));
 }
 
 void XrSessionCoordinator::EndSession() {
diff --git a/components/webxr/android/xr_session_coordinator.h b/components/webxr/android/xr_session_coordinator.h
index cbb47ee..89e6e95 100644
--- a/components/webxr/android/xr_session_coordinator.h
+++ b/components/webxr/android/xr_session_coordinator.h
@@ -55,8 +55,11 @@
       int render_process_id,
       int render_frame_id) override;
 
-  void RequestXrSession(ActivityReadyCallback ready_callback,
-                        device::JavaShutdownCallback shutdown_callback);
+  void RequestXrSession(
+      int render_process_id,
+      int render_frame_id,
+      ActivityReadyCallback ready_callback,
+      device::JavaShutdownCallback shutdown_callback);
 
   // Methods called from the Java side.
   void OnDrawingSurfaceReady(
diff --git a/content/app/content_main_runner_impl.cc b/content/app/content_main_runner_impl.cc
index 1fe400f..391e7ce8 100644
--- a/content/app/content_main_runner_impl.cc
+++ b/content/app/content_main_runner_impl.cc
@@ -512,9 +512,7 @@
 // TODO(crbug.com/1173395): Also enable for Lacros-Chrome.
 #if BUILDFLAG(IS_CHROMEOS)
   // The consumer should only be enabled when the delegate allows it.
-  std::unique_ptr<TracingDelegate> delegate =
-      GetContentClient()->browser()->CreateTracingDelegate();
-  return delegate && delegate->IsSystemWideTracingEnabled();
+  return GetContentClient()->browser()->IsSystemWideTracingEnabled();
 #else   // BUILDFLAG(IS_CHROMEOS_ASH)
   return false;
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 2d18f9f..6b6eaf5 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -229,6 +229,7 @@
     "//services/viz/public/cpp/gpu",
     "//services/viz/public/mojom",
     "//services/webnn:webnn_service",
+    "//services/webnn:webnn_switches",
     "//skia",
     "//skia/public/mojom",
     "//storage/browser",
@@ -3266,6 +3267,12 @@
       "service_worker/service_worker_usb_delegate_observer.h",
 
       # Most speech code is non-Android.
+      "speech/endpointer/endpointer.cc",
+      "speech/endpointer/endpointer.h",
+      "speech/endpointer/energy_endpointer.cc",
+      "speech/endpointer/energy_endpointer.h",
+      "speech/endpointer/energy_endpointer_params.cc",
+      "speech/endpointer/energy_endpointer_params.h",
       "speech/network_speech_recognition_engine_impl.cc",
       "speech/network_speech_recognition_engine_impl.h",
       "speech/speech_recognition_engine.cc",
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index 1636bae..e164a66 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -11,6 +11,7 @@
 #include "base/containers/contains.h"
 #include "base/debug/dump_without_crashing.h"
 #include "base/logging.h"
+#include "base/memory/ptr_util.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
@@ -46,8 +47,7 @@
 std::unique_ptr<BrowserAccessibility> BrowserAccessibility::Create(
     BrowserAccessibilityManager* manager,
     ui::AXNode* node) {
-  return std::unique_ptr<BrowserAccessibility>(
-      new BrowserAccessibility(manager, node));
+  return base::WrapUnique(new BrowserAccessibility(manager, node));
 }
 #endif  // !BUILDFLAG(HAS_PLATFORM_ACCESSIBILITY_SUPPORT)
 
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index acacc03a..6deb03e9 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -11,6 +11,7 @@
 #include "base/functional/bind.h"
 #include "base/i18n/break_iterator.h"
 #include "base/lazy_instance.h"
+#include "base/memory/ptr_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -81,8 +82,7 @@
 std::unique_ptr<BrowserAccessibility> BrowserAccessibility::Create(
     BrowserAccessibilityManager* manager,
     ui::AXNode* node) {
-  return std::unique_ptr<BrowserAccessibilityAndroid>(
-      new BrowserAccessibilityAndroid(manager, node));
+  return base::WrapUnique(new BrowserAccessibilityAndroid(manager, node));
 }
 
 using UniqueIdMap = std::unordered_map<int32_t, BrowserAccessibilityAndroid*>;
diff --git a/content/browser/accessibility/browser_accessibility_android_unittest.cc b/content/browser/accessibility/browser_accessibility_android_unittest.cc
index 3a4f298..220c34c 100644
--- a/content/browser/accessibility/browser_accessibility_android_unittest.cc
+++ b/content/browser/accessibility/browser_accessibility_android_unittest.cc
@@ -11,7 +11,6 @@
 #include "content/browser/accessibility/browser_accessibility_manager.h"
 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
 #include "content/public/test/browser_task_environment.h"
-#include "content/public/test/scoped_accessibility_mode_override.h"
 #include "content/test/test_content_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/strings/grit/blink_accessibility_strings.h"
@@ -65,7 +64,6 @@
  private:
   void SetUp() override;
   MockContentClient client_;
-  std::unique_ptr<ScopedAccessibilityModeOverride> ax_mode_override_;
 
   // This is needed to prevent a DCHECK failure when OnAccessibilityApiUsage
   // is called in BrowserAccessibility::GetRole.
@@ -80,8 +78,6 @@
   test_browser_accessibility_delegate_ =
       std::make_unique<ui::TestAXPlatformTreeManagerDelegate>();
   SetContentClient(&client_);
-  ax_mode_override_ =
-      std::make_unique<ScopedAccessibilityModeOverride>(ui::kAXModeComplete);
 }
 
 TEST_F(BrowserAccessibilityAndroidTest, TestRetargetTextOnly) {
diff --git a/content/browser/accessibility/browser_accessibility_mac.mm b/content/browser/accessibility/browser_accessibility_mac.mm
index 3d607c07..5e72bd2e 100644
--- a/content/browser/accessibility/browser_accessibility_mac.mm
+++ b/content/browser/accessibility/browser_accessibility_mac.mm
@@ -7,6 +7,7 @@
 #import <Cocoa/Cocoa.h>
 
 #include "base/debug/stack_trace.h"
+#include "base/memory/ptr_util.h"
 #include "base/memory/scoped_policy.h"
 #import "base/task/single_thread_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
@@ -20,8 +21,7 @@
 std::unique_ptr<BrowserAccessibility> BrowserAccessibility::Create(
     BrowserAccessibilityManager* manager,
     ui::AXNode* node) {
-  return std::unique_ptr<BrowserAccessibilityMac>(
-      new BrowserAccessibilityMac(manager, node));
+  return base::WrapUnique(new BrowserAccessibilityMac(manager, node));
 }
 
 BrowserAccessibilityMac::BrowserAccessibilityMac(
diff --git a/content/browser/accessibility/browser_accessibility_manager_mac.h b/content/browser/accessibility/browser_accessibility_manager_mac.h
index 9d13de6..7b1265e 100644
--- a/content/browser/accessibility/browser_accessibility_manager_mac.h
+++ b/content/browser/accessibility/browser_accessibility_manager_mac.h
@@ -50,6 +50,13 @@
   void FireGeneratedEvent(ui::AXEventGenerator::Event event_type,
                           const ui::AXNode* node) override;
 
+  void FireAriaNotificationEvent(
+      BrowserAccessibility* node,
+      const std::string& announcement,
+      const std::string& notification_id,
+      ax::mojom::AriaNotificationInterrupt interrupt_property,
+      ax::mojom::AriaNotificationPriority priority_property) override;
+
   bool OnAccessibilityEvents(
       const AXEventNotificationDetails& details) override;
 
diff --git a/content/browser/accessibility/browser_accessibility_manager_mac.mm b/content/browser/accessibility/browser_accessibility_manager_mac.mm
index e9e46e9..4ff37a0 100644
--- a/content/browser/accessibility/browser_accessibility_manager_mac.mm
+++ b/content/browser/accessibility/browser_accessibility_manager_mac.mm
@@ -431,6 +431,37 @@
   FireNativeMacNotification(mac_notification, wrapper);
 }
 
+void BrowserAccessibilityManagerMac::FireAriaNotificationEvent(
+    BrowserAccessibility* node,
+    const std::string& announcement,
+    const std::string& notification_id,
+    ax::mojom::AriaNotificationInterrupt interrupt_property,
+    ax::mojom::AriaNotificationPriority priority_property) {
+  DCHECK(node);
+
+  auto* root_manager = GetManagerForRootFrame();
+  if (!root_manager) {
+    return;
+  }
+
+  auto* root_manager_mac = root_manager->ToBrowserAccessibilityManagerMac();
+
+  auto MapPropertiesToNSAccessibilityPriorityLevel =
+      [&]() -> NSAccessibilityPriorityLevel {
+    switch (priority_property) {
+      case ax::mojom::AriaNotificationPriority::kNone:
+        return NSAccessibilityPriorityMedium;
+      case ax::mojom::AriaNotificationPriority::kImportant:
+        return NSAccessibilityPriorityHigh;
+    }
+    NOTREACHED_NORETURN();
+  };
+
+  PostAnnouncementNotification(base::SysUTF8ToNSString(announcement),
+                               [root_manager_mac->GetParentView() window],
+                               MapPropertiesToNSAccessibilityPriorityLevel());
+}
+
 void BrowserAccessibilityManagerMac::FireNativeMacNotification(
     NSString* mac_notification,
     BrowserAccessibility* node) {
diff --git a/content/browser/accessibility/browser_accessibility_unittest.cc b/content/browser/accessibility/browser_accessibility_unittest.cc
index 464d09f0..c139385 100644
--- a/content/browser/accessibility/browser_accessibility_unittest.cc
+++ b/content/browser/accessibility/browser_accessibility_unittest.cc
@@ -7,7 +7,6 @@
 #include "build/build_config.h"
 #include "content/browser/accessibility/browser_accessibility_manager.h"
 #include "content/public/test/browser_task_environment.h"
-#include "content/public/test/scoped_accessibility_mode_override.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/ax_node_position.h"
 #include "ui/accessibility/platform/test_ax_platform_tree_manager_delegate.h"
@@ -33,11 +32,9 @@
   void SetUp() override;
 
   BrowserTaskEnvironment task_environment_;
-  ScopedAccessibilityModeOverride ax_mode_override_;
 };
 
-BrowserAccessibilityTest::BrowserAccessibilityTest()
-    : ax_mode_override_(ui::kAXModeComplete) {}
+BrowserAccessibilityTest::BrowserAccessibilityTest() {}
 
 BrowserAccessibilityTest::~BrowserAccessibilityTest() = default;
 
diff --git a/content/browser/accessibility/browser_accessibility_win.cc b/content/browser/accessibility/browser_accessibility_win.cc
index 08d84cd70..ee2c6912 100644
--- a/content/browser/accessibility/browser_accessibility_win.cc
+++ b/content/browser/accessibility/browser_accessibility_win.cc
@@ -4,10 +4,10 @@
 
 #include "content/browser/accessibility/browser_accessibility_win.h"
 
+#include "base/memory/ptr_util.h"
 #include "content/browser/accessibility/browser_accessibility_manager.h"
 #include "content/browser/accessibility/browser_accessibility_manager_win.h"
 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
-
 #include "ui/base/win/atl_module.h"
 
 namespace content {
@@ -16,8 +16,7 @@
 std::unique_ptr<BrowserAccessibility> BrowserAccessibility::Create(
     BrowserAccessibilityManager* manager,
     ui::AXNode* node) {
-  return std::unique_ptr<BrowserAccessibilityWin>(
-      new BrowserAccessibilityWin(manager, node));
+  return base::WrapUnique(new BrowserAccessibilityWin(manager, node));
 }
 
 BrowserAccessibilityWin::BrowserAccessibilityWin(
diff --git a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
index 1e86947..f444171 100644
--- a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
+++ b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
@@ -2210,7 +2210,8 @@
 
 #if defined(IS_FAST_BUILD)  // Avoid flakiness on slower debug/sanitizer builds.
 // TODO(crbug.com/1179057): Fix disabled flaky test.
-#if BUILDFLAG(IS_LINUX)
+// TODO(crbug.com/332652840): It is flaky with SkiaGraphite enabled on Windows.
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
 #define MAYBE_DocumentSelectionChangesAreNotBatched \
   DISABLED_DocumentSelectionChangesAreNotBatched
 #else
@@ -2266,7 +2267,8 @@
 
 #if defined(IS_FAST_BUILD)  // Avoid flakiness on slower debug/sanitizer builds.
 // TODO(crbug.com/1179057): Fix disabled flaky test.
-#if BUILDFLAG(IS_LINUX)
+// TODO(crbug.com/332652840): It is flaky with SkiaGraphite enabled on Windows.
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
 #define MAYBE_ActiveDescendantChangesAreNotBatched \
   DISABLED_ActiveDescendantChangesAreNotBatched
 #else
diff --git a/content/browser/background_sync/background_sync_manager.cc b/content/browser/background_sync/background_sync_manager.cc
index 65e5da4..e73729c 100644
--- a/content/browser/background_sync/background_sync_manager.cc
+++ b/content/browser/background_sync/background_sync_manager.cc
@@ -316,7 +316,7 @@
     case blink::ServiceWorkerStatusCode::kErrorTimeout:
       return "timeout";
     default:
-      NOTREACHED();
+      DUMP_WILL_BE_NOTREACHED_NORETURN();
       return "unknown error";
   }
 }
diff --git a/content/browser/browsing_data/clear_site_data_handler_browsertest.cc b/content/browser/browsing_data/clear_site_data_handler_browsertest.cc
index 6a9d4e31..80920812 100644
--- a/content/browser/browsing_data/clear_site_data_handler_browsertest.cc
+++ b/content/browser/browsing_data/clear_site_data_handler_browsertest.cc
@@ -94,11 +94,22 @@
 // ClearSiteData.
 class TestBrowsingDataRemoverDelegate : public MockBrowsingDataRemoverDelegate {
  public:
+  // TODO(crbug.com/328043119): Remove code associated with
+  // kAncestorChainBitEnabledInPartitionedCookies after it's enabled by default.
+  TestBrowsingDataRemoverDelegate() {
+    feature_list_.InitAndEnableFeature(
+        net::features::kAncestorChainBitEnabledInPartitionedCookies);
+  }
   // Sets a test expectation that a Clear-Site-Data header call from |origin|
   // (under |top_level_site|) instructing to delete |cookies|, |storage|, and
   // |cache|, will schedule the corresponding BrowsingDataRemover deletion
   // tasks. If |set_storage_key|=kYes (the default) then a storage key will be
   // set on the filter builder.
+  //
+  // `cookie_partition_key_third_party` is true when cookie partition key is
+  // expected to be third party due to the expected ancestor chain bit value
+  // indicating cross-site even if the `top_level_site` and origin are
+  // same-site.
   void ExpectClearSiteDataCall(
       const StoragePartitionConfig& storage_partition_config,
       const url::Origin& origin,
@@ -106,6 +117,7 @@
       bool cookies,
       bool storage,
       bool cache,
+      bool cookie_partition_key_third_party = false,
       SetStorageKey set_storage_key = SetStorageKey::kYes) {
     const uint64_t kOriginTypeMask =
         BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB |
@@ -116,9 +128,10 @@
           BrowsingDataRemover::DATA_TYPE_COOKIES |
           BrowsingDataRemover::DATA_TYPE_AVOID_CLOSING_CONNECTIONS;
       net::CookiePartitionKey::AncestorChainBit ancestor_chain_bit =
-          (net::SchemefulSite(origin) == top_level_site
-               ? net::CookiePartitionKey::AncestorChainBit::kSameSite
-               : net::CookiePartitionKey::AncestorChainBit::kCrossSite);
+          cookie_partition_key_third_party ||
+                  (net::SchemefulSite(origin) != top_level_site)
+              ? net::CookiePartitionKey::AncestorChainBit::kCrossSite
+              : net::CookiePartitionKey::AncestorChainBit::kSameSite;
       BrowsingDataFilterBuilderImpl filter_builder(
           BrowsingDataFilterBuilder::Mode::kDelete);
       filter_builder.AddRegisterableDomain(origin.host());
@@ -161,9 +174,15 @@
   // A shortcut for the above method, but with only cookies deleted, and
   // |origin|'s site is used as |top_level_site| if omitted. This is useful for
   // most tests that use |kClearCookiesHeader|.
+  //
+  // `cookie_partition_key_third_party` is true when cookie partition key is
+  // expected to be third party due to the expected ancestor chain bit value
+  // indicating cross-site even if the `top_level_site` and origin are
+  // same-site.
   void ExpectClearSiteDataCookiesCall(
       const StoragePartitionConfig& storage_partition_config,
       const url::Origin& origin,
+      bool cookie_partition_key_third_party = false,
       base::optional_ref<const net::SchemefulSite> top_level_site =
           base::optional_ref<const net::SchemefulSite>()) {
     ExpectClearSiteDataCall(storage_partition_config, origin,
@@ -172,8 +191,11 @@
                                 : net::SchemefulSite(origin),
                             /*cookies=*/true,
                             /*storage=*/false,
-                            /*cache=*/false);
+                            /*cache=*/false, cookie_partition_key_third_party);
   }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
 };
 
 }  // namespace
@@ -428,7 +450,8 @@
 
       if (mask & (1 << i))
         delegate()->ExpectClearSiteDataCookiesCall(
-            storage_partition_config(), url::Origin::Create(urls[i]));
+            storage_partition_config(), url::Origin::Create(urls[i]),
+            /*cookie_partition_key_third_party=*/i != 0);
     }
 
     // Set up redirects between urls 0 --> 1 --> 2.
@@ -479,6 +502,7 @@
       if (mask & (1 << i))
         delegate()->ExpectClearSiteDataCookiesCall(
             storage_partition_config(), url::Origin::Create(urls[i]),
+            /*cookie_partition_key_third_party=*/true,
             net::SchemefulSite(page_with_image));
     }
 
@@ -629,18 +653,18 @@
   // but not by the "/resource_from_sw" fetch. |origin3| and |origin4| prove
   // that the number of calls is dependent on the number of network responses,
   // i.e. that it isn't always 1 as in the case of |origin1| and |origin2|.
-  delegate()->ExpectClearSiteDataCookiesCall(storage_partition_config(),
-                                             url::Origin::Create(origin1),
-                                             net::SchemefulSite(url));
-  delegate()->ExpectClearSiteDataCookiesCall(storage_partition_config(),
-                                             url::Origin::Create(origin4),
-                                             net::SchemefulSite(url));
-  delegate()->ExpectClearSiteDataCookiesCall(storage_partition_config(),
-                                             url::Origin::Create(origin2),
-                                             net::SchemefulSite(url));
-  delegate()->ExpectClearSiteDataCookiesCall(storage_partition_config(),
-                                             url::Origin::Create(origin4),
-                                             net::SchemefulSite(url));
+  delegate()->ExpectClearSiteDataCookiesCall(
+      storage_partition_config(), url::Origin::Create(origin1),
+      /*cookie_partition_key_third_party=*/false, net::SchemefulSite(url));
+  delegate()->ExpectClearSiteDataCookiesCall(
+      storage_partition_config(), url::Origin::Create(origin4),
+      /*cookie_partition_key_third_party=*/false, net::SchemefulSite(url));
+  delegate()->ExpectClearSiteDataCookiesCall(
+      storage_partition_config(), url::Origin::Create(origin2),
+      /*cookie_partition_key_third_party=*/true, net::SchemefulSite(url));
+  delegate()->ExpectClearSiteDataCookiesCall(
+      storage_partition_config(), url::Origin::Create(origin4),
+      /*cookie_partition_key_third_party=*/false, net::SchemefulSite(url));
 
   url = https_server()->GetURL("origin1.com", "/anything-in-workers-scope");
   AddQuery(&url, "origin1", origin1.spec());
@@ -710,9 +734,9 @@
     AddQuery(&page, "html", content);
 
     if (test_case.should_run)
-      delegate()->ExpectClearSiteDataCookiesCall(storage_partition_config(),
-                                                 url::Origin::Create(resource),
-                                                 net::SchemefulSite(page));
+      delegate()->ExpectClearSiteDataCookiesCall(
+          storage_partition_config(), url::Origin::Create(resource),
+          /*cookie_partition_key_third_party=*/false, net::SchemefulSite(page));
 
     EXPECT_TRUE(NavigateToURL(shell(), page));
     WaitForTitle(shell(), "done");
@@ -975,7 +999,7 @@
   EXPECT_TRUE(NavigateToURL(shell(), url));
   delegate()->ExpectClearSiteDataCall(
       storage_partition_config(), url::Origin::Create(url),
-      net::SchemefulSite(url), false, true, false, SetStorageKey::kNo);
+      net::SchemefulSite(url), false, true, false, false, SetStorageKey::kNo);
   SetClearSiteDataHeader("\"storage\"");
   EXPECT_FALSE(RunScriptAndGetBool("installServiceWorker()"));
   delegate()->VerifyAndClearExpectations();
@@ -1003,7 +1027,7 @@
   // Update the service worker and send C-S-D during update.
   delegate()->ExpectClearSiteDataCall(
       storage_partition_config(), url::Origin::Create(url),
-      net::SchemefulSite(url), false, true, false, SetStorageKey::kNo);
+      net::SchemefulSite(url), false, true, false, false, SetStorageKey::kNo);
 
   base::RunLoop loop;
   auto* remover = browser_context()->GetBrowsingDataRemover();
diff --git a/content/browser/cookie_store/cookie_store_manager_unittest.cc b/content/browser/cookie_store/cookie_store_manager_unittest.cc
index 7e0cb870..eab838c 100644
--- a/content/browser/cookie_store/cookie_store_manager_unittest.cc
+++ b/content/browser/cookie_store/cookie_store_manager_unittest.cc
@@ -1843,8 +1843,12 @@
 // cookies with third-party cookie blocking on.
 TEST_F(CookieStoreManagerTest, PartitionedWorker_FirstPartyPartition) {
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatures({net::features::kThirdPartyStoragePartitioning},
-                                {});
+  // TODO(crbug.com/328043119): Remove code associated with
+  // kAncestorChainBitEnabledInPartitionedCookies after it's enabled by default.
+  feature_list.InitWithFeatures(
+      {net::features::kThirdPartyStoragePartitioning,
+       net::features::kAncestorChainBitEnabledInPartitionedCookies},
+      {});
 
   // Register 1P worker.
   int64_t first_party_registration_id =
@@ -1879,7 +1883,9 @@
           /*secure=*/true,
           /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
           net::COOKIE_PRIORITY_DEFAULT,
-          net::CookiePartitionKey::FromURLForTesting(GURL(kExampleScope)))));
+          net::CookiePartitionKey::FromURLForTesting(
+              GURL(kExampleScope),
+              net::CookiePartitionKey::AncestorChainBit::kSameSite))));
   task_environment_.RunUntilIdle();
 
   EXPECT_EQ(1u, worker_test_helper_->changes().size());
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
index 512b9d9..61876f1 100644
--- a/content/browser/gpu/gpu_process_host.cc
+++ b/content/browser/gpu/gpu_process_host.cc
@@ -81,6 +81,7 @@
 #include "sandbox/policy/mojom/sandbox.mojom.h"
 #include "sandbox/policy/sandbox_type.h"
 #include "sandbox/policy/switches.h"
+#include "services/webnn/webnn_switches.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/base/ui_base_switches.h"
@@ -284,6 +285,7 @@
     sandbox::policy::switches::kDisableMetalShaderCache,
     switches::kShowMacOverlayBorders,
     switches::kUseHighGPUThreadPriorityForPerfTests,
+    switches::kWebNNCoreMlDumpModel,
 #endif
 #if BUILDFLAG(IS_OZONE)
     switches::kOzonePlatform,
diff --git a/content/browser/indexed_db/indexed_db_browsertest.cc b/content/browser/indexed_db/indexed_db_browsertest.cc
index bd55b844..65ba4056 100644
--- a/content/browser/indexed_db/indexed_db_browsertest.cc
+++ b/content/browser/indexed_db/indexed_db_browsertest.cc
@@ -818,6 +818,36 @@
   EXPECT_EQ(0, RequestUsage());
 }
 
+// Regression test for crbug.com/330868483
+// In this test,
+//   1. the page reads a blob
+//   2. the backing store is force-closed
+//   3. the reference to the blob is GC'd
+//      . this disconnects the IndexedDBDataItemReader *after* the backing store
+//        is already reset
+//   4. the page reads the same blob, reusing the IndexedDBDataItemReader
+//   5. the blob reference is dropped and GC'd again
+//   6. don't crash
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithGCExposed, ForceCloseWithBlob) {
+  const GURL kTestUrl = GetTestUrl("indexeddb", "write_and_read_blob.html");
+  SimpleTest(kTestUrl);
+  DeleteBucketData(
+      blink::StorageKey::CreateFirstParty(url::Origin::Create(kTestUrl)));
+  std::ignore = EvalJs(shell(), "gc()");
+
+  // Run the test again, but don't reset the object stores first to make sure
+  // the same blob is read again.
+  std::ignore = EvalJs(shell(), "testThenGc()");
+  while (true) {
+    std::string result = shell()->web_contents()->GetLastCommittedURL().ref();
+    if (!result.empty()) {
+      EXPECT_EQ(result, "pass");
+      break;
+    }
+    base::RunLoop().RunUntilIdle();
+  }
+}
+
 IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DeleteBucketDataIncognito) {
   const GURL test_url = GetTestUrl("indexeddb", "fill_up_5k.html");
   const blink::StorageKey kTestStorageKey =
diff --git a/content/browser/indexed_db/indexed_db_bucket_context.cc b/content/browser/indexed_db/indexed_db_bucket_context.cc
index 0630256..0482e10b 100644
--- a/content/browser/indexed_db/indexed_db_bucket_context.cc
+++ b/content/browser/indexed_db/indexed_db_bucket_context.cc
@@ -296,8 +296,6 @@
     leveldb_env::Options in_memory_options = base_options;
     in_memory_options.env = in_memory_env.get();
     in_memory_options.paranoid_checks = false;
-    in_memory_options.create_if_missing = true;
-    in_memory_options.write_buffer_size = 4 * 1024 * 1024;
     std::unique_ptr<leveldb::DB> db;
     leveldb::Status status =
         leveldb_env::OpenDB(in_memory_options, std::string(), &db);
@@ -1352,10 +1350,11 @@
 
   auto itr = file_reader_map_.find(path);
   if (itr == file_reader_map_.end()) {
+    // Unretained is safe because `this` owns the reader.
     auto reader = std::make_unique<IndexedDBDataItemReader>(
         path, expected_modification_time,
         base::BindOnce(&IndexedDBBucketContext::RemoveBoundReaders,
-                       weak_factory_.GetWeakPtr()),
+                       base::Unretained(this)),
         io_task_runner_);
     itr = file_reader_map_
               .insert({path, std::make_tuple(std::move(reader),
@@ -1704,6 +1703,7 @@
 }
 
 void IndexedDBBucketContext::ResetBackingStore() {
+  file_reader_map_.clear();
   weak_factory_.InvalidateWeakPtrs();
 
   if (backing_store_) {
diff --git a/content/browser/push_messaging/push_messaging_manager.cc b/content/browser/push_messaging/push_messaging_manager.cc
index 2bec11b..dd164e83 100644
--- a/content/browser/push_messaging/push_messaging_manager.cc
+++ b/content/browser/push_messaging/push_messaging_manager.cc
@@ -701,9 +701,10 @@
     case blink::ServiceWorkerStatusCode::kErrorInvalidArguments:
     case blink::ServiceWorkerStatusCode::kErrorStorageDisconnected:
     case blink::ServiceWorkerStatusCode::kErrorStorageDataCorrupted: {
-      NOTREACHED() << "Got unexpected error code: "
-                   << static_cast<uint32_t>(service_worker_status) << " "
-                   << blink::ServiceWorkerStatusToString(service_worker_status);
+      DUMP_WILL_BE_NOTREACHED_NORETURN()
+          << "Got unexpected error code: "
+          << static_cast<uint32_t>(service_worker_status) << " "
+          << blink::ServiceWorkerStatusToString(service_worker_status);
       get_status = blink::mojom::PushGetRegistrationStatus::STORAGE_ERROR;
       break;
     }
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index d264e39..c5bd2cf 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -16276,9 +16276,9 @@
 
   std::string debug_string = ToDebugString();
   SCOPED_CRASH_KEY_STRING256("shutdown", "frame->ToDebugString", debug_string);
-  NOTREACHED() << "BrowserContext->ShutdownStarted() without first closing all "
-               << "WebContents; debug_string = " << debug_string;
-  base::debug::DumpWithoutCrashing();
+  DUMP_WILL_BE_NOTREACHED_NORETURN()
+      << "BrowserContext->ShutdownStarted() without first closing all "
+      << "WebContents; debug_string = " << debug_string;
 }
 
 blink::StorageKey RenderFrameHostImpl::GetBucketStorageKey() {
diff --git a/content/browser/renderer_host/render_widget_host_view_base.cc b/content/browser/renderer_host/render_widget_host_view_base.cc
index 7a96c85..50de77d9 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -511,7 +511,7 @@
 void RenderWidgetHostViewBase::ProcessAckedTouchEvent(
     const TouchEventWithLatencyInfo& touch,
     blink::mojom::InputEventResultState ack_result) {
-  NOTREACHED();
+  DUMP_WILL_BE_NOTREACHED_NORETURN();
 }
 
 // Send system cursor size to the renderer via UpdateScreenInfo().
diff --git a/components/speech/endpointer/endpointer.cc b/content/browser/speech/endpointer/endpointer.cc
similarity index 89%
rename from components/speech/endpointer/endpointer.cc
rename to content/browser/speech/endpointer/endpointer.cc
index 7d9518ad..32d767fe 100644
--- a/components/speech/endpointer/endpointer.cc
+++ b/content/browser/speech/endpointer/endpointer.cc
@@ -1,8 +1,8 @@
-// Copyright 2024 The Chromium Authors
+// Copyright 2012 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/speech/endpointer/endpointer.h"
+#include "content/browser/speech/endpointer/endpointer.h"
 
 #include "base/time/time.h"
 #include "components/speech/audio_buffer.h"
@@ -10,9 +10,9 @@
 namespace {
 const int64_t kMicrosecondsPerSecond = base::Time::kMicrosecondsPerSecond;
 const int kFrameRate = 50;  // 1 frame = 20ms of audio.
-}  // namespace
+}
 
-namespace speech {
+namespace content {
 
 Endpointer::Endpointer(int sample_rate)
     : speech_input_possibly_complete_silence_length_us_(-1),
@@ -61,7 +61,7 @@
   waiting_for_speech_complete_timeout_ = false;
   speech_previously_detected_ = false;
   speech_input_complete_ = false;
-  audio_frame_time_us_ = 0;  // Reset time for packets sent to endpointer.
+  audio_frame_time_us_ = 0; // Reset time for packets sent to endpointer.
   speech_end_time_us_ = -1;
   speech_start_time_us_ = -1;
 }
@@ -99,8 +99,10 @@
   int sample_index = 0;
   while (sample_index + frame_size_ <= num_samples) {
     // Have the endpointer process the frame.
-    energy_endpointer_.ProcessAudioFrame(
-        audio_frame_time_us_, audio_data + sample_index, frame_size_, rms_out);
+    energy_endpointer_.ProcessAudioFrame(audio_frame_time_us_,
+                                         audio_data + sample_index,
+                                         frame_size_,
+                                         rms_out);
     sample_index += frame_size_;
     audio_frame_time_us_ +=
         (frame_size_ * kMicrosecondsPerSecond) / sample_rate_;
@@ -131,7 +133,7 @@
       // Speech possibly complete timeout.
       if ((waiting_for_speech_possibly_complete_timeout_) &&
           (ep_time - speech_end_time_us_ >
-           speech_input_possibly_complete_silence_length_us_)) {
+              speech_input_possibly_complete_silence_length_us_)) {
         waiting_for_speech_possibly_complete_timeout_ = false;
       }
       if (waiting_for_speech_complete_timeout_) {
@@ -147,7 +149,8 @@
           requested_silence_length =
               long_speech_input_complete_silence_length_us_;
         } else {
-          requested_silence_length = speech_input_complete_silence_length_us_;
+          requested_silence_length =
+              speech_input_complete_silence_length_us_;
         }
 
         // Speech complete timeout.
@@ -162,4 +165,4 @@
   return ep_status;
 }
 
-}  // namespace speech
+}  // namespace content
diff --git a/components/speech/endpointer/endpointer.h b/content/browser/speech/endpointer/endpointer.h
similarity index 89%
rename from components/speech/endpointer/endpointer.h
rename to content/browser/speech/endpointer/endpointer.h
index db54ea0..7695d5ec 100644
--- a/components/speech/endpointer/endpointer.h
+++ b/content/browser/speech/endpointer/endpointer.h
@@ -1,18 +1,19 @@
-// Copyright 2024 The Chromium Authors
+// Copyright 2012 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_SPEECH_ENDPOINTER_ENDPOINTER_H_
-#define COMPONENTS_SPEECH_ENDPOINTER_ENDPOINTER_H_
+#ifndef CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
+#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
 
 #include <stdint.h>
 
-#include "components/speech/endpointer/energy_endpointer.h"
+#include "content/browser/speech/endpointer/energy_endpointer.h"
+#include "content/common/content_export.h"
 
 class EpStatus;
 class AudioChunk;
 
-namespace speech {
+namespace content {
 
 // A simple interface to the underlying energy-endpointer implementation, this
 // class lets callers provide audio as being recorded and let them poll to find
@@ -42,7 +43,7 @@
 // The timeout length is speech_input_complete_silence_length until
 // long_speech_length, when it changes to
 // long_speech_input_complete_silence_length.
-class Endpointer {
+class CONTENT_EXPORT Endpointer {
  public:
   explicit Endpointer(int sample_rate);
 
@@ -69,7 +70,9 @@
 
   // Returns true if the endpointer detected reasonable audio levels above
   // background noise which could be user speech, false if not.
-  bool DidStartReceivingSpeech() const { return speech_previously_detected_; }
+  bool DidStartReceivingSpeech() const {
+    return speech_previously_detected_;
+  }
 
   bool IsEstimatingEnvironment() const {
     return energy_endpointer_.estimating_environment();
@@ -91,7 +94,9 @@
     long_speech_length_us_ = time_us;
   }
 
-  bool speech_input_complete() const { return speech_input_complete_; }
+  bool speech_input_complete() const {
+    return speech_input_complete_;
+  }
 
   // RMS background noise level in dB.
   float NoiseLevelDb() const { return energy_endpointer_.GetNoiseLevelDb(); }
@@ -143,6 +148,6 @@
   int32_t frame_size_;
 };
 
-}  // namespace speech
+}  // namespace content
 
-#endif  // COMPONENTS_SPEECH_ENDPOINTER_ENDPOINTER_H_
+#endif  // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
diff --git a/components/speech/endpointer/endpointer_unittest.cc b/content/browser/speech/endpointer/endpointer_unittest.cc
similarity index 92%
rename from components/speech/endpointer/endpointer_unittest.cc
rename to content/browser/speech/endpointer/endpointer_unittest.cc
index 0f7c2cd..c00a161e 100644
--- a/components/speech/endpointer/endpointer_unittest.cc
+++ b/content/browser/speech/endpointer/endpointer_unittest.cc
@@ -1,26 +1,25 @@
-// Copyright 2024 The Chromium Authors
+// Copyright 2012 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/speech/endpointer/endpointer.h"
-
 #include <stdint.h>
 
 #include "base/memory/raw_ptr.h"
 #include "components/speech/audio_buffer.h"
+#include "content/browser/speech/endpointer/endpointer.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
-const int kFrameRate = 50;     // 20 ms long frames for AMR encoding.
+const int kFrameRate = 50;  // 20 ms long frames for AMR encoding.
 const int kSampleRate = 8000;  // 8 k samples per second for AMR encoding.
 
 // At 8 sample per second a 20 ms frame is 160 samples, which corrsponds
 // to the AMR codec.
 const int kFrameSize = kSampleRate / kFrameRate;  // 160 samples.
 static_assert(kFrameSize == 160, "invalid frame size");
-}  // namespace
+}
 
-namespace speech {
+namespace content {
 
 class FrameProcessor {
  public:
@@ -52,7 +51,7 @@
     // Create random samples.
     for (int i = 0; i < kFrameSize; ++i) {
       float randNum = static_cast<float>(rand() - (RAND_MAX / 2)) /
-                      static_cast<float>(RAND_MAX);
+          static_cast<float>(RAND_MAX);
       samples[i] = static_cast<int16_t>(gain * randNum);
     }
 
@@ -60,15 +59,12 @@
     time += static_cast<int64_t>(kFrameSize * (1e6 / kSampleRate));
 
     // Log the status.
-    if (20 == frame_count) {
+    if (20 == frame_count)
       EXPECT_EQ(EP_PRE_SPEECH, ep_status);
-    }
-    if (70 == frame_count) {
+    if (70 == frame_count)
       EXPECT_EQ(EP_SPEECH_PRESENT, ep_status);
-    }
-    if (120 == frame_count) {
+    if (120 == frame_count)
       EXPECT_EQ(EP_PRE_SPEECH, ep_status);
-    }
   }
 }
 
@@ -158,4 +154,4 @@
   endpointer.EndSession();
 }
 
-}  // namespace speech
+}  // namespace content
diff --git a/components/speech/endpointer/energy_endpointer.cc b/content/browser/speech/endpointer/energy_endpointer.cc
similarity index 87%
rename from components/speech/endpointer/energy_endpointer.cc
rename to content/browser/speech/endpointer/energy_endpointer.cc
index f8dba53..ec72e2b7 100644
--- a/components/speech/endpointer/energy_endpointer.cc
+++ b/content/browser/speech/endpointer/energy_endpointer.cc
@@ -1,4 +1,4 @@
-// Copyright 2024 The Chromium Authors
+// Copyright 2012 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
@@ -6,7 +6,7 @@
 // based of, see
 // https://wiki.corp.google.com/twiki/bin/view/Main/ChromeGoogleCodeXRef
 
-#include "components/speech/endpointer/energy_endpointer.h"
+#include "content/browser/speech/endpointer/energy_endpointer.h"
 
 #include <math.h>
 #include <stddef.h>
@@ -35,15 +35,14 @@
 }
 
 float GetDecibel(float value) {
-  if (value > 1.0e-100) {
+  if (value > 1.0e-100)
     return 20 * log10(value);
-  }
   return -2000.0;
 }
 
 }  // namespace
 
-namespace speech {
+namespace content {
 
 // Stores threshold-crossing histories for making decisions about the speech
 // state.
@@ -81,7 +80,7 @@
 void EnergyEndpointer::HistoryRing::SetRing(int size, bool initial_state) {
   insertion_index_ = 0;
   decision_points_.clear();
-  DecisionPoint init = {-1, initial_state};
+  DecisionPoint init = { -1, initial_state };
   decision_points_.resize(size, init);
 }
 
@@ -93,39 +92,33 @@
 
 int64_t EnergyEndpointer::HistoryRing::EndTime() const {
   int ind = insertion_index_ - 1;
-  if (ind < 0) {
+  if (ind < 0)
     ind = decision_points_.size() - 1;
-  }
   return decision_points_[ind].time_us;
 }
 
 float EnergyEndpointer::HistoryRing::RingSum(float duration_sec) {
-  if (decision_points_.empty()) {
+  if (decision_points_.empty())
     return 0.0;
-  }
 
   int64_t sum_us = 0;
   int ind = insertion_index_ - 1;
-  if (ind < 0) {
+  if (ind < 0)
     ind = decision_points_.size() - 1;
-  }
   int64_t end_us = decision_points_[ind].time_us;
   bool is_on = decision_points_[ind].decision;
   int64_t start_us =
       end_us - static_cast<int64_t>(0.5 + (1.0e6 * duration_sec));
-  if (start_us < 0) {
+  if (start_us < 0)
     start_us = 0;
-  }
   size_t n_summed = 1;  // n points ==> (n-1) intervals
   while ((decision_points_[ind].time_us > start_us) &&
          (n_summed < decision_points_.size())) {
     --ind;
-    if (ind < 0) {
+    if (ind < 0)
       ind = decision_points_.size() - 1;
-    }
-    if (is_on) {
+    if (is_on)
       sum_us += end_us - decision_points_[ind].time_us;
-    }
     is_on = decision_points_[ind].decision;
     end_us = decision_points_[ind].time_us;
     n_summed++;
@@ -149,9 +142,11 @@
       rms_adapt_(0),
       start_lag_(0),
       end_lag_(0),
-      user_input_start_time_us_(0) {}
+      user_input_start_time_us_(0) {
+}
 
-EnergyEndpointer::~EnergyEndpointer() {}
+EnergyEndpointer::~EnergyEndpointer() {
+}
 
 int EnergyEndpointer::TimeToFrame(float time) const {
   return static_cast<int32_t>(0.5 + (time / params_.frame_period()));
@@ -186,19 +181,16 @@
   // depends upon ep_frame_period being set correctly in the factory
   // that did this instantiation.
   max_window_dur_ = params_.onset_window();
-  if (params_.speech_on_window() > max_window_dur_) {
+  if (params_.speech_on_window() > max_window_dur_)
     max_window_dur_ = params_.speech_on_window();
-  }
-  if (params_.offset_window() > max_window_dur_) {
+  if (params_.offset_window() > max_window_dur_)
     max_window_dur_ = params_.offset_window();
-  }
   Restart(true);
 
-  offset_confirm_dur_sec_ =
-      params_.offset_window() - params_.offset_confirm_dur();
-  if (offset_confirm_dur_sec_ < 0.0) {
+  offset_confirm_dur_sec_ = params_.offset_window() -
+                            params_.offset_confirm_dur();
+  if (offset_confirm_dur_sec_ < 0.0)
     offset_confirm_dur_sec_ = 0.0;
-  }
 
   user_input_start_time_us_ = 0;
 
@@ -216,10 +208,10 @@
   frame_counter_ = 0;  // Used for rapid initial update of levels.
 
   sample_rate_ = params_.sample_rate();
-  start_lag_ =
-      static_cast<int>(sample_rate_ / params_.max_fundamental_frequency());
-  end_lag_ =
-      static_cast<int>(sample_rate_ / params_.min_fundamental_frequency());
+  start_lag_ = static_cast<int>(sample_rate_ /
+                                params_.max_fundamental_frequency());
+  end_lag_ = static_cast<int>(sample_rate_ /
+                              params_.min_fundamental_frequency());
 }
 
 void EnergyEndpointer::StartSession() {
@@ -276,9 +268,8 @@
         if (tsum > params_.onset_confirm_dur()) {
           status_ = EP_SPEECH_PRESENT;
         } else {  // If signal is not maintained, drop back to pre-speech.
-          if (tsum <= params_.onset_detect_dur()) {
+          if (tsum <= params_.onset_detect_dur())
             status_ = EP_PRE_SPEECH;
-          }
         }
         break;
       }
@@ -288,9 +279,8 @@
         // smaller residency time in the on_ring, than was required to
         // enter the SPEECH_PERSENT state.
         float on_time = history_->RingSum(params_.speech_on_window());
-        if (on_time < params_.on_maintain_dur()) {
+        if (on_time < params_.on_maintain_dur())
           status_ = EP_POSSIBLE_OFFSET;
-        }
         break;
       }
 
@@ -303,9 +293,8 @@
           status_ = EP_PRE_SPEECH;  // Automatically reset for next utterance.
         } else {  // If speech picks up again we allow return to SPEECH_PRESENT.
           if (history_->RingSum(params_.speech_on_window()) >=
-              params_.on_maintain_dur()) {
+              params_.on_maintain_dur())
             status_ = EP_SPEECH_PRESENT;
-          }
         }
         break;
 
@@ -331,25 +320,23 @@
         } else {
           rms_adapt_ = (0.95f * rms_adapt_) + (0.05f * rms);
         }
-        float target_threshold = 0.3f * rms_adapt_ + noise_level_;
-        decision_threshold_ =
-            (.90f * decision_threshold_) + (0.10f * target_threshold);
+        float target_threshold = 0.3f * rms_adapt_ +  noise_level_;
+        decision_threshold_ = (.90f * decision_threshold_) +
+                              (0.10f * target_threshold);
       }
     }
 
     // Set a floor
-    if (decision_threshold_ < params_.min_decision_threshold()) {
+    if (decision_threshold_ < params_.min_decision_threshold())
       decision_threshold_ = params_.min_decision_threshold();
-    }
   }
 
   // Update speech and noise levels.
   UpdateLevels(rms);
   ++frame_counter_;
 
-  if (rms_out) {
+  if (rms_out)
     *rms_out = GetDecibel(rms);
-  }
 }
 
 float EnergyEndpointer::GetNoiseLevelDb() const {
@@ -363,7 +350,7 @@
     // Alpha increases from 0 to (k-1)/k where k is the number of time
     // steps in the initial adaptation period.
     float alpha = static_cast<float>(frame_counter_) /
-                  static_cast<float>(fast_update_frames_);
+        static_cast<float>(fast_update_frames_);
     noise_level_ = (alpha * noise_level_) + ((1 - alpha) * rms);
     DVLOG(1) << "FAST UPDATE, frame_counter_ " << frame_counter_
              << ", fast_update_frames_ " << fast_update_frames_;
@@ -371,18 +358,16 @@
     // Update Noise level. The noise level adapts quickly downward, but
     // slowly upward. The noise_level_ parameter is not currently used
     // for threshold adaptation. It is used for UI feedback.
-    if (noise_level_ < rms) {
+    if (noise_level_ < rms)
       noise_level_ = (0.999f * noise_level_) + (0.001f * rms);
-    } else {
+    else
       noise_level_ = (0.95f * noise_level_) + (0.05f * rms);
-    }
   }
   if (estimating_environment_ || (frame_counter_ < fast_update_frames_)) {
-    decision_threshold_ = noise_level_ * 2;  // 6dB above noise level.
+    decision_threshold_ = noise_level_ * 2; // 6dB above noise level.
     // Set a floor
-    if (decision_threshold_ < params_.min_decision_threshold()) {
+    if (decision_threshold_ < params_.min_decision_threshold())
       decision_threshold_ = params_.min_decision_threshold();
-    }
   }
 }
 
@@ -391,4 +376,4 @@
   return status_;
 }
 
-}  // namespace speech
+}  // namespace content
diff --git a/components/speech/endpointer/energy_endpointer.h b/content/browser/speech/endpointer/energy_endpointer.h
similarity index 89%
rename from components/speech/endpointer/energy_endpointer.h
rename to content/browser/speech/endpointer/energy_endpointer.h
index bbea6a9..9f869a3 100644
--- a/components/speech/endpointer/energy_endpointer.h
+++ b/content/browser/speech/endpointer/energy_endpointer.h
@@ -1,4 +1,4 @@
-// Copyright 2024 The Chromium Authors
+// Copyright 2012 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -34,17 +34,18 @@
 // accept. The false accepts can be ignored by setting
 // ep_contamination_rejection_period.
 
-#ifndef COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
-#define COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
+#ifndef CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
+#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
 
 #include <stdint.h>
 
 #include <memory>
 #include <vector>
 
-#include "components/speech/endpointer/energy_endpointer_params.h"
+#include "content/browser/speech/endpointer/energy_endpointer_params.h"
+#include "content/common/content_export.h"
 
-namespace speech {
+namespace content {
 
 // Endpointer status codes
 enum EpStatus {
@@ -55,7 +56,7 @@
   EP_POST_SPEECH,
 };
 
-class EnergyEndpointer {
+class CONTENT_EXPORT EnergyEndpointer {
  public:
   // The default construction MUST be followed by Init(), before any
   // other use can be made of the instance.
@@ -93,7 +94,9 @@
   // corresponding to the most recently computed frame.
   EpStatus Status(int64_t* status_time_us) const;
 
-  bool estimating_environment() const { return estimating_environment_; }
+  bool estimating_environment() const {
+    return estimating_environment_;
+  }
 
   // Returns estimated noise level in dB.
   float GetNoiseLevelDb() const;
@@ -113,7 +116,7 @@
   // the 'time' (in seconds).
   int TimeToFrame(float time) const;
 
-  EpStatus status_;               // The current state of this instance.
+  EpStatus status_;  // The current state of this instance.
   float offset_confirm_dur_sec_;  // max on time allowed to confirm POST_SPEECH
   int64_t
       endpointer_time_us_;  // Time of the most recently received audio frame.
@@ -122,7 +125,7 @@
   int64_t
       frame_counter_;     // Number of frames seen. Used for initial adaptation.
   float max_window_dur_;  // Largest search window size (seconds)
-  float sample_rate_;     // Sampling rate.
+  float sample_rate_;  // Sampling rate.
 
   // Ring buffers to hold the speech activity history.
   std::unique_ptr<HistoryRing> history_;
@@ -154,6 +157,6 @@
   int64_t user_input_start_time_us_;
 };
 
-}  // namespace speech
+}  // namespace content
 
-#endif  // COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
+#endif  // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
diff --git a/components/speech/endpointer/energy_endpointer_params.cc b/content/browser/speech/endpointer/energy_endpointer_params.cc
similarity index 91%
rename from components/speech/endpointer/energy_endpointer_params.cc
rename to content/browser/speech/endpointer/energy_endpointer_params.cc
index 21194c41..d9b70f29 100644
--- a/components/speech/endpointer/energy_endpointer_params.cc
+++ b/content/browser/speech/endpointer/energy_endpointer_params.cc
@@ -1,10 +1,10 @@
-// Copyright 2024 The Chromium Authors
+// Copyright 2012 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/speech/endpointer/energy_endpointer_params.h"
+#include "content/browser/speech/endpointer/energy_endpointer_params.h"
 
-namespace speech {
+namespace content {
 
 EnergyEndpointerParams::EnergyEndpointerParams() {
   SetDefaults();
@@ -50,4 +50,4 @@
   contamination_rejection_period_ = source.contamination_rejection_period();
 }
 
-}  //  namespace speech
+}  //  namespace content
diff --git a/components/speech/endpointer/energy_endpointer_params.h b/content/browser/speech/endpointer/energy_endpointer_params.h
similarity index 79%
rename from components/speech/endpointer/energy_endpointer_params.h
rename to content/browser/speech/endpointer/energy_endpointer_params.h
index b5341c3..bf13dd2 100644
--- a/components/speech/endpointer/energy_endpointer_params.h
+++ b/content/browser/speech/endpointer/energy_endpointer_params.h
@@ -1,14 +1,16 @@
-// Copyright 2024 The Chromium Authors
+// Copyright 2012 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
-#define COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
+#ifndef CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
+#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
 
-namespace speech {
+#include "content/common/content_export.h"
+
+namespace content {
 
 // Input parameters for the EnergyEndpointer class.
-class EnergyEndpointerParams {
+class CONTENT_EXPORT EnergyEndpointerParams {
  public:
   EnergyEndpointerParams();
 
@@ -18,7 +20,9 @@
 
   // Accessors and mutators
   float frame_period() const { return frame_period_; }
-  void set_frame_period(float frame_period) { frame_period_ = frame_period; }
+  void set_frame_period(float frame_period) {
+    frame_period_ = frame_period;
+  }
 
   float frame_duration() const { return frame_duration_; }
   void set_frame_duration(float frame_duration) {
@@ -100,16 +104,16 @@
   }
 
  private:
-  float frame_period_;            // Frame period
-  float frame_duration_;          // Window size
-  float onset_window_;            // Interval scanned for onset activity
-  float speech_on_window_;        // Inverval scanned for ongoing speech
-  float offset_window_;           // Interval scanned for offset evidence
-  float offset_confirm_dur_;      // Silence duration required to confirm offset
-  float decision_threshold_;      // Initial rms detection threshold
+  float frame_period_;  // Frame period
+  float frame_duration_;  // Window size
+  float onset_window_;  // Interval scanned for onset activity
+  float speech_on_window_;  // Inverval scanned for ongoing speech
+  float offset_window_;  // Interval scanned for offset evidence
+  float offset_confirm_dur_;  // Silence duration required to confirm offset
+  float decision_threshold_;  // Initial rms detection threshold
   float min_decision_threshold_;  // Minimum rms detection threshold
-  float fast_update_dur_;         // Period for initial estimation of levels.
-  float sample_rate_;             // Expected sample rate.
+  float fast_update_dur_;  // Period for initial estimation of levels.
+  float sample_rate_;  // Expected sample rate.
 
   // Time to add on either side of endpoint threshold crossings
   float endpoint_margin_;
@@ -128,6 +132,6 @@
   float contamination_rejection_period_;
 };
 
-}  //  namespace speech
+}  //  namespace content
 
-#endif  // COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
+#endif  // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
diff --git a/content/browser/speech/speech_recognizer_impl.h b/content/browser/speech/speech_recognizer_impl.h
index 84d94cbc..9972dca 100644
--- a/content/browser/speech/speech_recognizer_impl.h
+++ b/content/browser/speech/speech_recognizer_impl.h
@@ -10,7 +10,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
-#include "components/speech/endpointer/endpointer.h"
+#include "content/browser/speech/endpointer/endpointer.h"
 #include "content/browser/speech/speech_recognition_engine.h"
 #include "content/browser/speech/speech_recognizer.h"
 #include "content/common/content_export.h"
@@ -172,7 +172,7 @@
 
   raw_ptr<media::AudioSystem, DanglingUntriaged> audio_system_;
   std::unique_ptr<SpeechRecognitionEngine> recognition_engine_;
-  speech::Endpointer endpointer_;
+  Endpointer endpointer_;
   scoped_refptr<media::AudioCapturerSource> audio_capturer_source_;
   int num_samples_recorded_;
   float audio_level_;
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 8ef9465..e5e3cd4 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -2444,6 +2444,10 @@
         permission_delegate_->GrantSharingPermission(
             origin(), GetEmbeddingOrigin(), url::Origin::Create(idp_config_url),
             account_id_);
+      } else {
+        permission_delegate_->RefreshExistingSharingPermission(
+            origin(), GetEmbeddingOrigin(), url::Origin::Create(idp_config_url),
+            account_id_);
       }
 
       SetRequiresUserMediation(false);
diff --git a/content/browser/webid/test/mock_permission_delegate.h b/content/browser/webid/test/mock_permission_delegate.h
index 87f8d8568..be94399 100644
--- a/content/browser/webid/test/mock_permission_delegate.h
+++ b/content/browser/webid/test/mock_permission_delegate.h
@@ -51,6 +51,11 @@
                     const url::Origin&,
                     const url::Origin&,
                     const std::string&));
+  MOCK_METHOD4(RefreshExistingSharingPermission,
+               void(const url::Origin&,
+                    const url::Origin&,
+                    const url::Origin&,
+                    const std::string&));
   MOCK_METHOD1(GetIdpSigninStatus, std::optional<bool>(const url::Origin&));
   MOCK_METHOD2(SetIdpSigninStatus, void(const url::Origin&, bool));
   MOCK_METHOD1(RegisterIdP, void(const ::GURL&));
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index f8b41ff..3888089 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -832,6 +832,10 @@
   return nullptr;
 }
 
+bool ContentBrowserClient::IsSystemWideTracingEnabled() {
+  return false;
+}
+
 bool ContentBrowserClient::IsPluginAllowedToCallRequestOSFileHandle(
     BrowserContext* browser_context,
     const GURL& url) {
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 520d6201..0956279 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -1374,6 +1374,10 @@
   // It's valid to return nullptr.
   virtual std::unique_ptr<TracingDelegate> CreateTracingDelegate();
 
+  // Whether system-wide performance trace collection using the external system
+  // tracing service is enabled.
+  virtual bool IsSystemWideTracingEnabled();
+
   // Returns true if plugin referred to by the url can use
   // pp::FileIO::RequestOSFileHandle.
   virtual bool IsPluginAllowedToCallRequestOSFileHandle(
diff --git a/content/public/browser/federated_identity_permission_context_delegate.h b/content/public/browser/federated_identity_permission_context_delegate.h
index 67880cf..8c418d4 100644
--- a/content/public/browser/federated_identity_permission_context_delegate.h
+++ b/content/public/browser/federated_identity_permission_context_delegate.h
@@ -72,6 +72,14 @@
       const url::Origin& identity_provider,
       const std::string& account_id) = 0;
 
+  // Refreshes an existing sharing permission. Updates the timestamp
+  // corresponding to the last time in which the sharing permission was used.
+  virtual void RefreshExistingSharingPermission(
+      const url::Origin& relying_party_requester,
+      const url::Origin& relying_party_embedder,
+      const url::Origin& identity_provider,
+      const std::string& account_id) = 0;
+
   // Returns whether the user is signed in with the IDP. If unknown, return
   // std::nullopt.
   virtual std::optional<bool> GetIdpSigninStatus(
diff --git a/content/public/browser/tracing_delegate.cc b/content/public/browser/tracing_delegate.cc
index e0a1c1e..5e886f7b 100644
--- a/content/public/browser/tracing_delegate.cc
+++ b/content/public/browser/tracing_delegate.cc
@@ -22,8 +22,4 @@
   return false;
 }
 
-bool TracingDelegate::IsSystemWideTracingEnabled() {
-  return false;
-}
-
 }  // namespace content
diff --git a/content/public/browser/tracing_delegate.h b/content/public/browser/tracing_delegate.h
index 3f6d131..4d81841 100644
--- a/content/public/browser/tracing_delegate.h
+++ b/content/public/browser/tracing_delegate.h
@@ -28,10 +28,6 @@
 
   // Specifies whether traces that aren't uploaded should still be saved.
   virtual bool ShouldSaveUnuploadedTrace() const;
-
-  // Whether system-wide performance trace collection using the external system
-  // tracing service is enabled.
-  virtual bool IsSystemWideTracingEnabled();
 };
 
 }  // namespace content
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index c4190f6..e8553ec 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -1254,6 +1254,14 @@
 
 #endif  // BUILDFLAG(IS_ANDROID)
 
+#if BUILDFLAG(IS_CHROMEOS)
+// If true, then GpuMemoryBuffer video frames are enabled only if HW support for
+// NV12 is present (as determined by the relevant command-line flags).
+BASE_FEATURE(kGateNV12GMBVideoFramesOnHWSupport,
+             "GateNV12GMBVideoFramesOnHWSupport",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+#endif
+
 #if BUILDFLAG(IS_MAC)
 // Enables backgrounding hidden renderers on Mac.
 BASE_FEATURE(kMacAllowBackgroundingRenderProcesses,
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 53e4a0a..eea5cdde 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -307,6 +307,10 @@
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebViewSuppressTapDuringFling);
 #endif  // BUILDFLAG(IS_ANDROID)
 
+#if BUILDFLAG(IS_CHROMEOS)
+CONTENT_EXPORT BASE_DECLARE_FEATURE(kGateNV12GMBVideoFramesOnHWSupport);
+#endif
+
 #if BUILDFLAG(IS_MAC)
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kMacAllowBackgroundingRenderProcesses);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kMacImeLiveConversionFix);
diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
index 3a1b293e..cbe4a06b 100644
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
@@ -19,6 +19,7 @@
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "content/public/common/content_features.h"
 #include "content/renderer/media/codec_factory.h"
 #include "content/renderer/render_thread_impl.h"
 #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
@@ -387,6 +388,14 @@
       !capabilities.image_ycbcr_420v_disabled_for_video_frames) {
     return OutputFormat::NV12_SINGLE_GMB;
   }
+
+#if BUILDFLAG(IS_CHROMEOS)
+  if (base::FeatureList::IsEnabled(
+          features::kGateNV12GMBVideoFramesOnHWSupport)) {
+    return OutputFormat::UNDEFINED;
+  }
+#endif
+
   if (capabilities.texture_rg) {
     return UseSingleNV12() ? OutputFormat::NV12_SINGLE_GMB
                            : OutputFormat::NV12_DUAL_GMB;
diff --git a/content/shell/browser/shell_browser_context.cc b/content/shell/browser/shell_browser_context.cc
index b9c645d..579db24 100644
--- a/content/shell/browser/shell_browser_context.cc
+++ b/content/shell/browser/shell_browser_context.cc
@@ -76,12 +76,6 @@
   if (cmd_line->HasSwitch(switches::kIgnoreCertificateErrors))
     ignore_certificate_errors_ = true;
 
-  // TODO(b/1295373): We are migrating from '--data-path' to '--user-data-dir'.
-  // Scripts use '--data-path' should be updated to use '--user-data-dir'.
-  if (cmd_line->HasSwitch(switches::kContentShellDataPath)) {
-    CHECK(cmd_line->HasSwitch(switches::kContentShellUserDataDir));
-  }
-
   if (cmd_line->HasSwitch(switches::kContentShellUserDataDir)) {
     path_ = cmd_line->GetSwitchValuePath(switches::kContentShellUserDataDir);
     if (base::DirectoryExists(path_) || base::CreateDirectory(path_))  {
diff --git a/content/shell/browser/shell_federated_permission_context.cc b/content/shell/browser/shell_federated_permission_context.cc
index 9aaeaa8..cc554809 100644
--- a/content/shell/browser/shell_federated_permission_context.cc
+++ b/content/shell/browser/shell_federated_permission_context.cc
@@ -178,6 +178,15 @@
   }
 }
 
+void ShellFederatedPermissionContext::RefreshExistingSharingPermission(
+    const url::Origin& relying_party_requester,
+    const url::Origin& relying_party_embedder,
+    const url::Origin& identity_provider,
+    const std::string& account_id) {
+  // `sharing_permissions_` does not currently store timestamps, so this method
+  // does nothing.
+}
+
 std::optional<bool> ShellFederatedPermissionContext::GetIdpSigninStatus(
     const url::Origin& idp_origin) {
   auto idp_signin_status = idp_signin_status_.find(idp_origin.Serialize());
diff --git a/content/shell/browser/shell_federated_permission_context.h b/content/shell/browser/shell_federated_permission_context.h
index 2193cb8c..852ff8a 100644
--- a/content/shell/browser/shell_federated_permission_context.h
+++ b/content/shell/browser/shell_federated_permission_context.h
@@ -78,6 +78,11 @@
                                const url::Origin& relying_party_embedder,
                                const url::Origin& identity_provider,
                                const std::string& account_id) override;
+  void RefreshExistingSharingPermission(
+      const url::Origin& relying_party_requester,
+      const url::Origin& relying_party_embedder,
+      const url::Origin& identity_provider,
+      const std::string& account_id) override;
   std::optional<bool> GetIdpSigninStatus(
       const url::Origin& idp_origin) override;
   void SetIdpSigninStatus(const url::Origin& idp_origin,
diff --git a/content/shell/common/shell_switches.cc b/content/shell/common/shell_switches.cc
index 148a3dd1..52d963a 100644
--- a/content/shell/common/shell_switches.cc
+++ b/content/shell/common/shell_switches.cc
@@ -8,10 +8,6 @@
 
 namespace switches {
 
-// TODO(b/1295373): Keep the old flag for a short period and remove it once we
-// are sure everything is ok.
-const char kContentShellDataPath[] = "data-path";
-
 // Makes Content Shell use the given path for its data directory.
 // NOTE: "user-data-dir" is used to align with Chromedriver's behavior. Please
 // do NOT change this to another value.
diff --git a/content/shell/common/shell_switches.h b/content/shell/common/shell_switches.h
index 7f5543c..d6c9234 100644
--- a/content/shell/common/shell_switches.h
+++ b/content/shell/common/shell_switches.h
@@ -11,7 +11,6 @@
 
 namespace switches {
 
-extern const char kContentShellDataPath[];
 extern const char kContentShellUserDataDir[];
 extern const char kCrashDumpsDir[];
 extern const char kDisableSystemFontCheck[];
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index d049eb8..0ca443a 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -3333,6 +3333,7 @@
       "../browser/host_zoom_map_impl_unittest.cc",
       "../browser/picture_in_picture/document_picture_in_picture_navigation_throttle_unittest.cc",
       "../browser/serial/serial_unittest.cc",
+      "../browser/speech/endpointer/endpointer_unittest.cc",
       "../browser/speech/network_speech_recognition_engine_impl_unittest.cc",
       "../browser/speech/speech_recognizer_impl_unittest.cc",
       "../browser/tracing/tracing_ui_unittest.cc",
diff --git a/content/test/data/indexeddb/write_and_read_blob.html b/content/test/data/indexeddb/write_and_read_blob.html
index 8e35494..5b986d3 100644
--- a/content/test/data/indexeddb/write_and_read_blob.html
+++ b/content/test/data/indexeddb/write_and_read_blob.html
@@ -29,6 +29,8 @@
   request.onerror = unexpectedErrorCallback;
 }
 
+let gcWhenDone = false;
+
 function getBlob() {
   const transaction = db.transaction('storeName', 'readwrite');
   transaction.onabort = unexpectedAbortCallback;
@@ -43,12 +45,19 @@
         fail(`expected blob to contain 'abc', got '${reader.result}'`);
         return;
       }
+      if (gcWhenDone) gc();
       done();
     });
     reader.readAsText(request.result.blob);
   }
 }
 
+function testThenGc() {
+  document.location.hash = '';
+  gcWhenDone = true;
+  test();
+}
+
 </script>
 </head>
 <body onLoad="test()">
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index b1ed631..f3fc292 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -874,6 +874,8 @@
 crbug.com/1221362 [ chromeos chromeos-board-amd64-generic ] conformance2/rendering/blitframebuffer-filter-outofbounds.html [ Failure ]
 crbug.com/1223542 [ chromeos chromeos-board-amd64-generic ] deqp/functional/gles3/framebufferblit/rect_03.html [ Failure ]
 crbug.com/1223542 [ chromeos chromeos-board-amd64-generic ] deqp/functional/gles3/framebufferblit/rect_04.html [ Failure ]
+crbug.com/332890145 [ chromeos chromeos-board-amd64-generic ] conformance2/extensions/ext-disjoint-timer-query-webgl2.html [ Failure ]
+
 crbug.com/326755442 [ chromeos intel mesa_ge_21.0 passthrough ] conformance2/extensions/ext-render-snorm.html [ Failure ]
 
 # Suspected i915 image uprev
diff --git a/content/utility/services.cc b/content/utility/services.cc
index cd13618..6c46c8d 100644
--- a/content/utility/services.cc
+++ b/content/utility/services.cc
@@ -42,6 +42,7 @@
 #if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
 #include "services/video_effects/public/mojom/video_effects_service.mojom.h"  // nogncheck
 #include "services/video_effects/video_effects_service_impl.h"  // nogncheck
+#include "services/video_effects/viz_gpu_channel_host_provider.h"  // nogncheck
 #endif
 
 #if BUILDFLAG(IS_MAC)
@@ -348,22 +349,6 @@
 }
 
 #if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
-
-class VizGpuChannelHostProvider : public video_effects::GpuChannelHostProvider {
- public:
-  explicit VizGpuChannelHostProvider(std::unique_ptr<viz::Gpu> viz_gpu)
-      : viz_gpu_(std::move(viz_gpu)) {
-    CHECK(viz_gpu_);
-  }
-
-  scoped_refptr<gpu::GpuChannelHost> GetGpuChannelHost() override {
-    return viz_gpu_->GetGpuChannel();
-  }
-
- private:
-  std::unique_ptr<viz::Gpu> viz_gpu_;
-};
-
 auto RunVideoEffects(
     mojo::PendingReceiver<video_effects::mojom::VideoEffectsService> receiver) {
   if (base::FeatureList::IsEnabled(media::kCameraMicEffects)) {
@@ -375,7 +360,8 @@
 
     return std::make_unique<video_effects::VideoEffectsServiceImpl>(
         std::move(receiver),
-        std::make_unique<VizGpuChannelHostProvider>(std::move(viz_gpu)));
+        std::make_unique<video_effects::VizGpuChannelHostProvider>(
+            std::move(viz_gpu)));
   }
 
   return std::unique_ptr<video_effects::VideoEffectsServiceImpl>{};
diff --git a/device/bluetooth/bluetooth_gatt_characteristic.h b/device/bluetooth/bluetooth_gatt_characteristic.h
index 406d85f..1b639c5 100644
--- a/device/bluetooth/bluetooth_gatt_characteristic.h
+++ b/device/bluetooth/bluetooth_gatt_characteristic.h
@@ -79,7 +79,7 @@
   typedef uint32_t Permissions;
 
   // Bluetooth Spec Vol 3, Part G, 3.3.3.3 Client Characteristic Configuration.
-  enum class NotificationType { kNotification = 1, kIndication };
+  enum class NotificationType : uint16_t { kNone, kNotification, kIndication };
 
   // The ErrorCallback is used by methods to asynchronously report errors.
   using ErrorCallback =
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic.cc
index 1c53074..c90d6cb 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic.cc
@@ -442,6 +442,10 @@
       return hasNotify;
     case NotificationType::kIndication:
       return hasIndicate;
+    case NotificationType::kNone:
+      LOG(WARNING) << __func__ << ": Unexpected NotificationType "
+                   << static_cast<uint16_t>(NotificationType::kNone);
+      return false;
   }
 }
 
diff --git a/device/bluetooth/floss/bluetooth_adapter_floss.cc b/device/bluetooth/floss/bluetooth_adapter_floss.cc
index 047eabe..3f3b6fa 100644
--- a/device/bluetooth/floss/bluetooth_adapter_floss.cc
+++ b/device/bluetooth/floss/bluetooth_adapter_floss.cc
@@ -1481,12 +1481,14 @@
     return false;
   }
 
+  bool confirm =
+      characteristic->CccdNotificationType() ==
+      device::BluetoothLocalGattCharacteristic::NotificationType::kIndication;
   std::string service_name =
       FlossDBusManager::Get()->GetGattManagerClient()->ServiceName();
   FlossDBusManager::Get()->GetGattManagerClient()->ServerSendNotification(
-      base::DoNothing(), service_name, characteristic->InstanceId(),
-      /*confirm=*/false, value);
-  // TODO(@sarveshkalwit) How to confirm success?
+      base::DoNothing(), service_name, characteristic->InstanceId(), confirm,
+      value);
   return true;
 }
 
diff --git a/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.cc b/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.cc
index 21fe420b..d7e8e92 100644
--- a/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.cc
+++ b/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.cc
@@ -384,4 +384,17 @@
   return descriptors_;
 }
 
+device::BluetoothGattCharacteristic::NotificationType
+BluetoothLocalGattCharacteristicFloss::CccdNotificationType() {
+  for (auto& descriptor : descriptors_) {
+    if (descriptor->GetUUID() == device::BluetoothGattDescriptor::
+                                     ClientCharacteristicConfigurationUuid()) {
+      return descriptor->CccdNotificationType();
+    }
+  }
+  LOG(WARNING) << __func__ << ": No CCCD found for characteristic with uuid "
+               << GetUUID();
+  return device::BluetoothGattCharacteristic::NotificationType::kNone;
+}
+
 }  // namespace floss
diff --git a/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.h b/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.h
index c86acab..08f1b9b 100644
--- a/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.h
+++ b/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.h
@@ -75,6 +75,7 @@
   int32_t InstanceId() const { return floss_instance_id_; }
   const std::vector<std::unique_ptr<BluetoothLocalGattDescriptorFloss>>&
   GetDescriptors() const;
+  NotificationType CccdNotificationType();
 
  private:
   friend class BluetoothLocalGattServiceFloss;
diff --git a/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.cc b/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.cc
index 2b6b7b60..6835a54 100644
--- a/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.cc
+++ b/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.cc
@@ -8,6 +8,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/rand_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/types/cxx23_to_underlying.h"
 #include "device/bluetooth/floss/bluetooth_gatt_characteristic_floss.h"
 #include "device/bluetooth/floss/floss_dbus_manager.h"
 
@@ -291,10 +292,13 @@
 
   auto properties = characteristic_->GetProperties();
   switch (notification_type) {
-    case CCCD_STOP_ALL:
+    case base::to_underlying(
+        device::BluetoothGattCharacteristic::NotificationType::kNone):
+      cccd_type_ = device::BluetoothGattCharacteristic::NotificationType::kNone;
       delegate->OnNotificationsStop(device, characteristic);
       break;
-    case CCCD_START_NOTIFY:
+    case base::to_underlying(
+        device::BluetoothGattCharacteristic::NotificationType::kNotification):
       if (!(properties &
             device::BluetoothGattCharacteristic::PROPERTY_NOTIFY)) {
         LOG(WARNING) << __func__ << ": Parent characteristic (uuid: "
@@ -304,12 +308,12 @@
                      << properties << ")";
         return GattStatus::kCccCfgErr;
       }
-      delegate->OnNotificationsStart(
-          device,
-          device::BluetoothGattCharacteristic::NotificationType::kNotification,
-          characteristic);
+      cccd_type_ =
+          device::BluetoothGattCharacteristic::NotificationType::kNotification;
+      delegate->OnNotificationsStart(device, cccd_type_, characteristic);
       break;
-    case CCCD_START_INDICATE:
+    case base::to_underlying(
+        device::BluetoothGattCharacteristic::NotificationType::kIndication):
       if (!(properties &
             device::BluetoothGattCharacteristic::PROPERTY_INDICATE)) {
         LOG(WARNING) << __func__ << ": Parent characteristic (uuid: "
@@ -319,10 +323,9 @@
                      << properties << ")";
         return GattStatus::kCccCfgErr;
       }
-      delegate->OnNotificationsStart(
-          device,
-          device::BluetoothGattCharacteristic::NotificationType::kIndication,
-          characteristic);
+      cccd_type_ =
+          device::BluetoothGattCharacteristic::NotificationType::kIndication;
+      delegate->OnNotificationsStart(device, cccd_type_, characteristic);
       break;
     default:
       LOG(WARNING) << __func__ << ": Value '" << notification_type
diff --git a/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.h b/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.h
index fd1321a9..410fe58 100644
--- a/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.h
+++ b/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.h
@@ -19,10 +19,6 @@
 
 namespace floss {
 
-inline constexpr uint16_t CCCD_STOP_ALL = 0x0000;
-inline constexpr uint16_t CCCD_START_NOTIFY = 0x0001;
-inline constexpr uint16_t CCCD_START_INDICATE = 0x0002;
-
 class BluetoothLocalGattCharacteristicFloss;
 
 // The BluetoothLocalGattDescriptorFloss class implements
@@ -70,6 +66,9 @@
 
   void ResolveInstanceId(const GattCharacteristic& characteristic);
   int32_t InstanceId() const { return floss_instance_id_; }
+  device::BluetoothGattCharacteristic::NotificationType CccdNotificationType() {
+    return cccd_type_;
+  }
 
  private:
   friend class BluetoothLocalGattCharacteristicFloss;
@@ -101,6 +100,9 @@
   GattStatus HandleCccDescriptor(std::string address,
                                  std::vector<uint8_t>& value);
 
+  // Notification type of the CCCD.
+  device::BluetoothGattCharacteristic::NotificationType cccd_type_;
+
   // Cached instance of the latest pending read/write request, if one exists.
   std::optional<GattRequest> pending_request_;
 
diff --git a/device/bluetooth/floss/floss_gatt_manager_client.h b/device/bluetooth/floss/floss_gatt_manager_client.h
index 9c88f0b0..49f9f57 100644
--- a/device/bluetooth/floss/floss_gatt_manager_client.h
+++ b/device/bluetooth/floss/floss_gatt_manager_client.h
@@ -376,7 +376,6 @@
   void RemoveObserver(FlossGattClientObserver* observer);
   void RemoveServerObserver(FlossGattServerObserver* observer);
 
-  // TODO(@sarveshkalwit): Rename client functions, ex. Connect->ClientConnect
   // Create a GATT client connection to a remote device on given transport.
   virtual void Connect(ResponseCallback<Void> callback,
                        const std::string& remote_device,
diff --git a/device/gamepad/nintendo_controller.cc b/device/gamepad/nintendo_controller.cc
index 134c463..04a167f 100644
--- a/device/gamepad/nintendo_controller.cc
+++ b/device/gamepad/nintendo_controller.cc
@@ -834,7 +834,7 @@
     default:
       break;
   }
-  NOTREACHED();
+  DUMP_WILL_BE_NOTREACHED_NORETURN();
   return GAMEPAD_BUS_UNKNOWN;
 }
 }  // namespace
diff --git a/device/vr/public/java/src/org/chromium/device/vr/XrFeatureStatus.java b/device/vr/public/java/src/org/chromium/device/vr/XrFeatureStatus.java
index 582dbb9..c337a89 100644
--- a/device/vr/public/java/src/org/chromium/device/vr/XrFeatureStatus.java
+++ b/device/vr/public/java/src/org/chromium/device/vr/XrFeatureStatus.java
@@ -7,12 +7,13 @@
 import org.jni_zero.CalledByNative;
 import org.jni_zero.JNINamespace;
 
-/** A wrapper */
+import org.chromium.base.PackageManagerUtils;
+
+/** A wrapper to allow querying feature status that depends on java state from native. */
 @JNINamespace("device")
 public class XrFeatureStatus {
     @CalledByNative
     public static boolean hasImmersiveFeature() {
-        // TODO(https://crbug.com/333511556): Implement this.
-        return false;
+        return PackageManagerUtils.hasSystemFeature(PackageManagerUtils.XR_IMMERSIVE_FEATURE_NAME);
     }
 }
diff --git a/docs/clion.md b/docs/clion.md
index f54320c..9582294 100644
--- a/docs/clion.md
+++ b/docs/clion.md
@@ -93,7 +93,7 @@
        `src/out/Default/chrome`
 1. Click `Ok` to close the dialog.
 
-### Configure `~/.gbinit`
+### Configure `~/.gdbinit`
 
 Before being able to debug, you need to configure `~/.gdbinit`. Open it with a
 text editor and enter:
diff --git a/docs/experiments/compression-dictionary-transport.md b/docs/experiments/compression-dictionary-transport.md
index 4597a340..71ee498518 100644
--- a/docs/experiments/compression-dictionary-transport.md
+++ b/docs/experiments/compression-dictionary-transport.md
@@ -155,15 +155,19 @@
 ## Supported HTTP protocol
 
 From Chrome 121, Chrome may not use stored shared dictionares when the
-connection is using HTTP/1 for non-localhost requests. Also Chrome may not use
-shared dictionares when the HTTPS connection's certificate is not rooted by a
-well known root CA (eg: using a user installed root certificate). This is for
-an investigation for an issue that some network appliances are interfering with
-HTTPS traffic by inspecting encrypted responses but failing to properly decode
-the shared dictionary encoded content.
+connection is using HTTP/1 for non-localhost requests. From Chrome 125, Chrome
+may not use stored shared dictionares when the connection is using HTTP/2 for
+non-localhost requests.
+Also Chrome may not use shared dictionares when the HTTPS connection's
+certificate is not rooted by a well known root CA (eg: using a user installed
+root certificate). This is for an investigation for an issue that some network
+appliances are interfering with HTTPS traffic by inspecting encrypted responses
+but failing to properly decode the shared dictionary encoded content.
 
 If you want to use shared dictionaries with HTTP/1, please enable
-[chrome://flags/#enable-compression-dictionary-transport-over-http1][over-http1-flag].
+[chrome://flags/#enable-compression-dictionary-transport-allow-http1][allow-http1-flag].
+If you want to use shared dictionaries with HTTP/2, please enable
+[chrome://flags/#enable-compression-dictionary-transport-allow-http2][allow-http2-flag].
 Also if you want to use shared dictionaries over the HTTPS connection which
 certificate is not rooted by a well known root CA, please disable
 [chrome://flags/#enable-compression-dictionary-transport-require-known-root-cert][require-known-root-ca-flag].
diff --git a/docs/telemetry_extension/api_overview.md b/docs/telemetry_extension/api_overview.md
index 0951ef4..346f2bf 100644
--- a/docs/telemetry_extension/api_overview.md
+++ b/docs/telemetry_extension/api_overview.md
@@ -276,7 +276,7 @@
 | Property Name | Type | Description |
 ------------ | ------- | ----------- |
 | uuid | string | UUID of the routine that entered this state  |
-| has_passed | boolean | Whether the routine finished successfully |
+| hasPassed | boolean | Whether the routine finished successfully |
 | detail | RoutineFinishedDetailUnion | Extra details about a finished routine |
 
 ### ExceptionInfo
@@ -295,6 +295,12 @@
 ### MemtesterResult
 | Property Name | Type | Description |
 ------------ | ------- | ----------- |
+| passedItems | Array<MemtesterTestItemEnum\> | Passed test items |
+| failedItems | Array<MemtesterTestItemEnum\> | Failed test items |
+
+### LegacyMemtesterResult
+| Property Name | Type | Description |
+------------ | ------- | ----------- |
 | passed_items | Array<MemtesterTestItemEnum\> | Passed test items |
 | failed_items | Array<MemtesterTestItemEnum\> | Failed test items |
 
@@ -310,7 +316,7 @@
 | uuid | string | UUID of the routine that entered this state  |
 | has_passed | boolean | Whether the routine finished successfully |
 | bytesTested | number | Number of bytes tested in the memory routine |
-| result | MemtesterResult | Contains the memtester test results |
+| result | LegacyMemtesterResult | Contains the memtester test results |
 
 ### CreateMemoryRoutineArguments
 | Property Name | Type | Description |
@@ -327,9 +333,9 @@
 ### FanRoutineFinishedDetail
 | Property Name | Type | Description |
 ------------ | ------- | ----------- |
-| passed_fan_ids | Array<number\> | The ids of fans that can be controlled |
-| failed_fan_ids | Array<number\> | The ids of fans that cannot be controlled |
-| fan_count_status | HardwarePresenceStatus | Whether the number of fan probed is matched |
+| passedFanIds | Array<number\> | The ids of fans that can be controlled |
+| failedFanIds | Array<number\> | The ids of fans that cannot be controlled |
+| fanCountStatus | HardwarePresenceStatus | Whether the number of fan probed is matched |
 
 ### LegacyFanRoutineFinishedInfo
 | Property Name | Type | Description |
@@ -356,12 +362,18 @@
 | uuid | string | UUID of the routine that entered this state  |
 | has_passed | boolean | Whether the routine finished successfully |
 
-### CreateVolumeButtonRoutineArguments
+### LegacyCreateVolumeButtonRoutineArguments
 | Property Name | Type | Description |
 ------------ | ------- | ----------- |
 | button_type | VolumeButtonType | The volume button to be tested |
 | timeout_seconds | number | Length of time to listen to the volume button events. The value should be positive and less or equal to 600 seconds |
 
+### CreateVolumeButtonRoutineArguments
+| Property Name | Type | Description |
+------------ | ------- | ----------- |
+| buttonType | VolumeButtonType | The volume button to be tested |
+| timeoutSeconds | number | Length of time to listen to the volume button events. The value should be positive and less or equal to 600 seconds |
+
 ### CreateRoutineArgumentsUnion
 This is a union type. Exactly one field is set.
 
@@ -401,10 +413,10 @@
 | cancelRoutine | (params: CancelRoutineRequest) => Promise<void\> | `os.diagnostics` | M119 | Stops executing the routine identified by `UUID` and removes all related resources from the system. |
 | createMemoryRoutine | (args: CreateMemoryRoutineArguments) => Promise<CreateRoutineResponse\> | `os.diagnostics` | M119 | (Deprecated in M125, use `createRoutine` with `memory` arg) Create a memory routine. |
 | createFanRoutine | (args: CreateFanRoutineArguments) => Promise<CreateRoutineResponse\> | `os.diagnostics` | M121 | (Deprecated in M125, use `createRoutine` with `fan` arg) Create a fan routine. |
-| createVolumeButtonRoutine | (args: CreateVolumeButtonRoutineArguments) => Promise<CreateRoutineResponse\> | `os.diagnostics` | M121 | (Deprecated in M125, use `createRoutine` with `volumeButton` arg) Create a volume button routine. |
+| createVolumeButtonRoutine | (args: LegacyCreateVolumeButtonRoutineArguments) => Promise<CreateRoutineResponse\> | `os.diagnostics` | M121 | (Deprecated in M125, use `createRoutine` with `volumeButton` arg) Create a volume button routine. |
 | isMemoryRoutineArgumentSupported | (args: CreateMemoryRoutineArguments) => Promise<RoutineSupportStatusInfo\> | `os.diagnostics` | M119 | (Deprecated in M125, use `isRoutineArgumentSupported` with `memory` arg) Checks whether a certain `CreateMemoryRoutineArguments` is supported on the platform. |
 | isFanRoutineArgumentSupported | (args: CreateFanRoutineArguments) => Promise<RoutineSupportStatusInfo\> | `os.diagnostics` | M121 | (Deprecated in M125, use `isRoutineArgumentSupported` with `fan` arg) Checks whether a certain `CreateFanRoutineArguments` is supported on the platform. |
-| isVolumeButtonRoutineArgumentSupported | (args: CreateVolumeButtonRoutineArguments) => Promise<RoutineSupportStatusInfo\> | `os.diagnostics` | M121 | (Deprecated in M125, use `isRoutineArgumentSupported` with `volumeButton` arg) Checks whether a certain `CreateVolumeButtonRoutineArguments` is supported on the platform. |
+| isVolumeButtonRoutineArgumentSupported | (args: LegacyCreateVolumeButtonRoutineArguments) => Promise<RoutineSupportStatusInfo\> | `os.diagnostics` | M121 | (Deprecated in M125, use `isRoutineArgumentSupported` with `volumeButton` arg) Checks whether a certain `LegacyCreateVolumeButtonRoutineArguments` is supported on the platform. |
 
 ## Events
 
diff --git a/docs/website b/docs/website
index b70b8d9..446e54b 160000
--- a/docs/website
+++ b/docs/website
@@ -1 +1 @@
-Subproject commit b70b8d9538282099b376bab14f18d63de1aa540a
+Subproject commit 446e54b44987857f3ef2f164d524e7d79dba64cc
diff --git a/extensions/browser/user_script_world_configuration_manager.cc b/extensions/browser/user_script_world_configuration_manager.cc
index 6d8c649..05b959d 100644
--- a/extensions/browser/user_script_world_configuration_manager.cc
+++ b/extensions/browser/user_script_world_configuration_manager.cc
@@ -8,6 +8,7 @@
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_prefs_factory.h"
+#include "extensions/browser/extension_registry_factory.h"
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/renderer_startup_helper.h"
 
@@ -27,6 +28,7 @@
             "UserScriptWorldConfigurationManager",
             BrowserContextDependencyManager::GetInstance()) {
     DependsOn(ExtensionPrefsFactory::GetInstance());
+    DependsOn(ExtensionRegistryFactory::GetInstance());
     DependsOn(RendererStartupHelperFactory::GetInstance());
   }
 
@@ -93,6 +95,7 @@
     : extension_prefs_(ExtensionPrefs::Get(browser_context)),
       renderer_helper_(
           RendererStartupHelperFactory::GetForBrowserContext(browser_context)) {
+  registry_observation_.Observe(ExtensionRegistry::Get(browser_context));
 }
 
 UserScriptWorldConfigurationManager::~UserScriptWorldConfigurationManager() =
@@ -199,4 +202,17 @@
   return factory.GetForBrowserContext(browser_context);
 }
 
+void UserScriptWorldConfigurationManager::OnExtensionWillBeInstalled(
+    content::BrowserContext* browser_context,
+    const Extension* extension,
+    bool is_update,
+    const std::string& old_name) {
+  if (!is_update) {
+    return;
+  }
+
+  extension_prefs_->UpdateExtensionPref(
+      extension->id(), kUserScriptsWorldsConfiguration.name, std::nullopt);
+}
+
 }  // namespace extensions
diff --git a/extensions/browser/user_script_world_configuration_manager.h b/extensions/browser/user_script_world_configuration_manager.h
index 1d8dec1..96af801 100644
--- a/extensions/browser/user_script_world_configuration_manager.h
+++ b/extensions/browser/user_script_world_configuration_manager.h
@@ -10,7 +10,10 @@
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension_id.h"
 #include "extensions/common/mojom/renderer.mojom.h"
 
@@ -29,7 +32,9 @@
 // configurations themselves are stored in the associated ExtensionPrefs.
 // Note: Like the ExtensionPrefs themselves, this class is shared between
 // on- and off-the-record contexts.
-class UserScriptWorldConfigurationManager : public KeyedService {
+class UserScriptWorldConfigurationManager
+    : public KeyedService,
+      public ExtensionRegistryObserver {
  public:
   explicit UserScriptWorldConfigurationManager(
       content::BrowserContext* browser_context);
@@ -66,6 +71,13 @@
       content::BrowserContext* browser_context);
 
  private:
+  // ExtensionRegistryObserver:
+  void OnExtensionWillBeInstalled(
+      content::BrowserContext* browser_context,
+      const Extension* extension,
+      bool is_update,
+      const std::string& old_name) override;
+
   // The ExtensionPrefs to use when setting or retrieving isolated world
   // configurations. Safe to use because this KeyedService depends on
   // ExtensionPrefs.
@@ -79,6 +91,9 @@
   // is always the accurate object to send updates through.
   // Always safe to use because this depends on it as a KeyedService.
   raw_ptr<RendererStartupHelper> renderer_helper_;
+
+  base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
+      registry_observation_{this};
 };
 
 }  // namespace extensions
diff --git a/extensions/renderer/api/messaging/gin_port.cc b/extensions/renderer/api/messaging/gin_port.cc
index 2594565c..8b9356f 100644
--- a/extensions/renderer/api/messaging/gin_port.cc
+++ b/extensions/renderer/api/messaging/gin_port.cc
@@ -77,7 +77,7 @@
   v8::Local<v8::Value> parsed_message =
       messaging_util::MessageToV8(context, message);
   if (parsed_message.IsEmpty()) {
-    NOTREACHED();
+    DUMP_WILL_BE_NOTREACHED_NORETURN();
     return;
   }
 
diff --git a/google_apis/calendar/calendar_api_requests.cc b/google_apis/calendar/calendar_api_requests.cc
index 1eff110..73cc7bc 100644
--- a/google_apis/calendar/calendar_api_requests.cc
+++ b/google_apis/calendar/calendar_api_requests.cc
@@ -26,11 +26,15 @@
 namespace {
 
 // For events without an event color, fill the colorId field with the color ID
-// provided.
+// provided by the calendar.
 void FillEmptyColorFields(EventList* events, const std::string& color) {
   for (auto& event : events->items()) {
     if (event->color_id().empty()) {
-      event->set_color_id(color);
+      // Events and calendars have some shared color IDs associated with
+      // different colors. Here we prepend the injected ID with a marker
+      // to indicate that the color ID does not represent an original event
+      // color.
+      event->set_color_id(kInjectedColorIdPrefix + color);
     }
   }
 }
@@ -202,7 +206,7 @@
       url_generator_(url_generator),
       start_time_(start_time),
       end_time_(end_time),
-      calendar_id_(kPrimaryCalendarID) {
+      calendar_id_(kPrimaryCalendarId) {
   CHECK(!callback_.is_null());
 }
 
diff --git a/google_apis/calendar/calendar_api_requests.h b/google_apis/calendar/calendar_api_requests.h
index fa272b4..08688c36 100644
--- a/google_apis/calendar/calendar_api_requests.h
+++ b/google_apis/calendar/calendar_api_requests.h
@@ -20,7 +20,9 @@
 
 namespace calendar {
 
-inline constexpr char kPrimaryCalendarID[] = "primary";
+// A marker to indicate an event has been injected with its calendar's colorId.
+inline const std::string kInjectedColorIdPrefix = "c";
+inline constexpr char kPrimaryCalendarId[] = "primary";
 
 // Callback used for requests that the server returns Calendar List
 // data formatted into JSON value.
diff --git a/google_apis/calendar/calendar_api_requests_unittest.cc b/google_apis/calendar/calendar_api_requests_unittest.cc
index f077e05..d57bd469 100644
--- a/google_apis/calendar/calendar_api_requests_unittest.cc
+++ b/google_apis/calendar/calendar_api_requests_unittest.cc
@@ -172,11 +172,14 @@
   base::Time::Exploded exploded;
   events->items()[0]->start_time().date_time().LocalExplode(&exploded);
   EXPECT_EQ(exploded.month, 11);
+  // Verifies that events containing a colorId do not have their color IDs
+  // replaced by calendar_color_id.
   EXPECT_EQ(events->items()[0]->color_id(), "3");
   EXPECT_EQ(events->items()[1]->color_id(), "3");
   // Verifies that an event without a colorId in the response yields an event
-  // object with a color ID equal to calendar_color_id.
-  EXPECT_EQ(events->items()[2]->color_id(), kTestCalendarColorId);
+  // object with a color ID equal to calendar_color_id (prepended by a marker).
+  EXPECT_EQ(events->items()[2]->color_id(),
+            calendar::kInjectedColorIdPrefix + kTestCalendarColorId);
 }
 
 // Tests that CalendarApiEventsRequest can generate the correct url and get the
diff --git a/google_apis/calendar/calendar_api_response_types.cc b/google_apis/calendar/calendar_api_response_types.cc
index 1b436b2f..9f5ff1c 100644
--- a/google_apis/calendar/calendar_api_response_types.cc
+++ b/google_apis/calendar/calendar_api_response_types.cc
@@ -55,7 +55,6 @@
 constexpr char kColorId[] = "colorId";
 constexpr char kEnd[] = "end";
 constexpr char kHtmlLink[] = "htmlLink";
-constexpr char kPathToCreatorSelf[] = "creator.self";
 constexpr char kStart[] = "start";
 constexpr char kStatus[] = "status";
 constexpr char kSummary[] = "summary";
@@ -90,8 +89,8 @@
 // (e.g. the value is structurally different from expected).
 bool ConvertEventStatus(const base::Value* value,
                         CalendarEvent::EventStatus* result) {
-  DCHECK(value);
-  DCHECK(result);
+  CHECK(value);
+  CHECK(result);
 
   const auto* status = value->GetIfString();
   if (!status) {
@@ -125,8 +124,19 @@
     // - user invited 2+ guests and removed themselves from the event (also,
     // the `attendeesOmitted` flag will be set to true).
 
-    const bool is_self_created =
-        event->FindBoolByDottedPath(kPathToCreatorSelf).value_or(false);
+    bool is_self_created = false;
+    // For non-primary-calendar events, a self-created event does not
+    // contain a creator field. Therefore, we assume that if an event
+    // is confirmed and has no attendees it is self-created.
+    const std::string* event_status = event->FindString(kStatus);
+    if (event_status) {
+      const auto it = kEventStatuses.find(*event_status);
+      if (it != kEventStatuses.end() &&
+          it->second == CalendarEvent::EventStatus::kConfirmed) {
+        is_self_created = true;
+      }
+    }
+
     const bool has_omitted_attendees =
         event->FindBool(kAttendeesOmitted).value_or(false);
 
diff --git a/google_apis/calendar/calendar_api_url_generator.cc b/google_apis/calendar/calendar_api_url_generator.cc
index 9dc4e4b..49c9253 100644
--- a/google_apis/calendar/calendar_api_url_generator.cc
+++ b/google_apis/calendar/calendar_api_url_generator.cc
@@ -56,7 +56,7 @@
   } else {
     url = base_url_.Resolve(
         base::StringPrintf(kCalendarV3EventsUrlFormat,
-                           base::EscapePath(kPrimaryCalendarID).c_str()));
+                           base::EscapePath(kPrimaryCalendarId).c_str()));
   }
   std::string start_time_string = util::FormatTimeAsString(start_time);
   std::string end_time_string = util::FormatTimeAsString(end_time);
diff --git a/google_apis/calendar/calendar_api_url_generator.h b/google_apis/calendar/calendar_api_url_generator.h
index 846eb953..ddc6d53 100644
--- a/google_apis/calendar/calendar_api_url_generator.h
+++ b/google_apis/calendar/calendar_api_url_generator.h
@@ -27,7 +27,7 @@
 
   // Returns a URL to fetch a list of calendar events.
   // |calendar_id|   ID of the calendar to fetch events from. If empty,
-  //                 kPrimaryCalendarID is used in its place.
+  //                 kPrimaryCalendarId is used in its place.
   // |start_time|    Start time of the event window
   // |end_time|      End time of the aforementioned window
   // |single_events| If true, expand recurring events into instances and only
diff --git a/google_apis/test/data/calendar/group_calendar_events.json b/google_apis/test/data/calendar/group_calendar_events.json
index eaa516c3..f7ee00c 100644
--- a/google_apis/test/data/calendar/group_calendar_events.json
+++ b/google_apis/test/data/calendar/group_calendar_events.json
@@ -52,6 +52,7 @@
       "updated": "2024-01-25T11:42:02.957Z",
       "summary": "Strawberry Refreshers @ 4th Floor Cafe",
       "description": "Need an afternoon pick-me-up? Come grab a strawberry refresher from the 4th Floor Cafe!",
+      "colorId": "7",
       "creator": {
         "email": "test2@google.com"
       },
diff --git a/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.cc
index 630b448b..f4bdf5042 100644
--- a/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.cc
@@ -867,9 +867,10 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> pixel_data) {
   return MakeBacking(mailbox, format, size, color_space, surface_origin,
-                     alpha_type, usage, std::move(debug_label), false,
+                     alpha_type, usage, std::move(debug_label), is_thread_safe,
                      pixel_data);
 }
 
diff --git a/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.h b/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.h
index 3a0beaa..7398ff9 100644
--- a/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.h
@@ -63,6 +63,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory_unittest.cc
index cfded14..aa0d75f2 100644
--- a/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory_unittest.cc
@@ -193,7 +193,7 @@
   uint32_t usage = SHARED_IMAGE_USAGE_DISPLAY_READ;
   auto backing = backing_factory_->CreateSharedImage(
       mailbox, format, size, color_space, surface_origin, alpha_type, usage,
-      "TestLabel", initial_data);
+      "TestLabel", /*is_thread_safe=*/false, initial_data);
   EXPECT_TRUE(backing);
 
   std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
diff --git a/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.cc
index cb25c9d..f8d60d1 100644
--- a/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.cc
@@ -82,6 +82,7 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> data) {
   auto backing = std::make_unique<AngleVulkanImageBacking>(
       context_state_, mailbox, format, size, color_space, surface_origin,
diff --git a/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.h b/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.h
index 980dec6..c10ff6f 100644
--- a/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.h
@@ -41,6 +41,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc b/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc
index 714e8e9b..53aa98b 100644
--- a/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc
@@ -59,6 +59,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override {
     return nullptr;
   }
diff --git a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory.cc
index 816ef1d..4155487 100644
--- a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory.cc
@@ -439,6 +439,7 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> pixel_data) {
   NOTIMPLEMENTED();
   return nullptr;
diff --git a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory.h b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory.h
index c0f0041..936b798 100644
--- a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory.h
@@ -98,6 +98,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image/dcomp_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/dcomp_image_backing_factory.cc
index 116e2b7..0934d84 100644
--- a/gpu/command_buffer/service/shared_image/dcomp_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/dcomp_image_backing_factory.cc
@@ -85,6 +85,7 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> pixel_data) {
   NOTREACHED();
   return nullptr;
diff --git a/gpu/command_buffer/service/shared_image/dcomp_image_backing_factory.h b/gpu/command_buffer/service/shared_image/dcomp_image_backing_factory.h
index ee1a62f2..b4350e7 100644
--- a/gpu/command_buffer/service/shared_image/dcomp_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/dcomp_image_backing_factory.h
@@ -53,6 +53,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image/egl_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/egl_image_backing_factory.cc
index 54aa33b..9bc356d 100644
--- a/gpu/command_buffer/service/shared_image/egl_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/egl_image_backing_factory.cc
@@ -78,6 +78,7 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> pixel_data) {
   return MakeEglImageBacking(mailbox, format, size, color_space, surface_origin,
                              alpha_type, usage, std::move(debug_label),
diff --git a/gpu/command_buffer/service/shared_image/egl_image_backing_factory.h b/gpu/command_buffer/service/shared_image/egl_image_backing_factory.h
index 2fd63a4..ef71d91 100644
--- a/gpu/command_buffer/service/shared_image/egl_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/egl_image_backing_factory.h
@@ -57,6 +57,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image/egl_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/egl_image_backing_factory_unittest.cc
index 5d51d292..c1e2f02 100644
--- a/gpu/command_buffer/service/shared_image/egl_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/egl_image_backing_factory_unittest.cc
@@ -447,7 +447,7 @@
   auto backing = backing_factory_->CreateSharedImage(
       mailbox, format, surface_handle, size, color_space,
       kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel",
-      /* is_thread_safe=*/true);
+      /*is_thread_safe=*/true);
   ASSERT_NE(backing, nullptr);
 
   std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
@@ -536,7 +536,8 @@
 
     auto backing = backing_factory_->CreateSharedImage(
         mailbox, format, size, color_space, kTopLeft_GrSurfaceOrigin,
-        kPremul_SkAlphaType, usage, "Dawn_SampledTexture", pixel_data);
+        kPremul_SkAlphaType, usage, "Dawn_SampledTexture",
+        /*is_thread_safe=*/true, pixel_data);
     ASSERT_NE(backing, nullptr);
 
     std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
@@ -687,7 +688,7 @@
         viz::ResourceSizes::CheckedSizeInBytes<unsigned int>(size_, format));
     backing_ = backing_factory->CreateSharedImage(
         mailbox_, format, size_, color_space, surface_origin, alpha_type, usage,
-        "TestLabel", initial_data);
+        "TestLabel", /*is_thread_safe=*/true, initial_data);
   } else {
     backing_ = backing_factory->CreateSharedImage(
         mailbox_, format, surface_handle, size_, color_space, surface_origin,
diff --git a/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.cc
index 03bf2c7..920d696 100644
--- a/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.cc
@@ -195,7 +195,7 @@
     uint32_t usage,
     std::string debug_label,
     bool is_thread_safe) {
-  DCHECK(!is_thread_safe);
+  CHECK(!is_thread_safe);
   return ExternalVkImageBacking::Create(
       context_state_, command_pool_.get(), mailbox, format, size, color_space,
       surface_origin, alpha_type, usage, std::move(debug_label),
@@ -212,7 +212,9 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> pixel_data) {
+  CHECK(!is_thread_safe);
   return ExternalVkImageBacking::Create(
       context_state_, command_pool_.get(), mailbox, format, size, color_space,
       surface_origin, alpha_type, usage, std::move(debug_label),
diff --git a/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.h b/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.h
index 8612afec..0bb5aec 100644
--- a/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.h
@@ -54,6 +54,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.cc
index 4f2e1f29..b07aef2 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.cc
@@ -71,7 +71,7 @@
     uint32_t usage,
     std::string debug_label,
     bool is_thread_safe) {
-  DCHECK(!is_thread_safe);
+  CHECK(!is_thread_safe);
   return CreateSharedImageInternal(
       mailbox, format, surface_handle, size, color_space, surface_origin,
       alpha_type, usage, std::move(debug_label), base::span<const uint8_t>());
@@ -87,7 +87,9 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> pixel_data) {
+  CHECK(!is_thread_safe);
   return CreateSharedImageInternal(mailbox, format, kNullSurfaceHandle, size,
                                    color_space, surface_origin, alpha_type,
                                    usage, std::move(debug_label), pixel_data);
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.h b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.h
index 4ba60288..ce7770a6 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.h
@@ -58,6 +58,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
index 19d005a8..8521391 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
@@ -607,7 +607,7 @@
 
   auto backing = backing_factory_->CreateSharedImage(
       mailbox, format, size, color_space, surface_origin, alpha_type, usage,
-      "TestLabel", initial_data);
+      "TestLabel", /*is_thread_safe=*/false, initial_data);
   ASSERT_TRUE(backing);
   EXPECT_TRUE(backing->IsCleared());
 
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.h b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.h
index 1e904c5..4115d35 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.h
@@ -64,6 +64,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.mm b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.mm
index 44b153c..e60b365 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.mm
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.mm
@@ -189,7 +189,7 @@
     uint32_t usage,
     std::string debug_label,
     bool is_thread_safe) {
-  DCHECK(!is_thread_safe);
+  CHECK(!is_thread_safe);
   return CreateSharedImageInternal(
       mailbox, format, surface_handle, size, color_space, surface_origin,
       alpha_type, usage, std::move(debug_label), base::span<const uint8_t>());
@@ -205,7 +205,9 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> pixel_data) {
+  CHECK(!is_thread_safe);
   return CreateSharedImageInternal(mailbox, format, kNullSurfaceHandle, size,
                                    color_space, surface_origin, alpha_type,
                                    usage, std::move(debug_label), pixel_data);
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
index 130621c..4dc5a22 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
@@ -986,7 +986,7 @@
 
   auto backing = backing_factory_->CreateSharedImage(
       mailbox, format, size, color_space, surface_origin, alpha_type, usage,
-      "TestLabel", initial_data);
+      "TestLabel", /*is_thread_safe=*/false, initial_data);
   ::testing::Mock::VerifyAndClearExpectations(&progress_reporter_);
   if (!should_succeed) {
     EXPECT_FALSE(backing);
@@ -1064,7 +1064,7 @@
   std::vector<uint8_t> initial_data(256 * 256 * 4);
   auto backing = backing_factory_->CreateSharedImage(
       mailbox, format, size, color_space, surface_origin, alpha_type, usage,
-      "TestLabel", initial_data);
+      "TestLabel", /*is_thread_safe=*/false, initial_data);
   if (!should_succeed) {
     EXPECT_FALSE(backing);
     return;
@@ -1128,11 +1128,11 @@
   std::vector<uint8_t> initial_data_large(256 * 512 * 4);
   auto backing = backing_factory_->CreateSharedImage(
       mailbox, format, size, color_space, surface_origin, alpha_type, usage,
-      "TestLabel", initial_data_small);
+      "TestLabel", /*is_thread_safe=*/false, initial_data_small);
   EXPECT_FALSE(backing);
   backing = backing_factory_->CreateSharedImage(
       mailbox, format, size, color_space, surface_origin, alpha_type, usage,
-      "TestLabel", initial_data_large);
+      "TestLabel", /*is_thread_safe=*/false, initial_data_large);
   EXPECT_FALSE(backing);
 }
 
@@ -1166,7 +1166,7 @@
   std::vector<uint8_t> initial_data(256 * 256 * 4);
   auto backing = backing_factory_->CreateSharedImage(
       mailbox, format, size, color_space, surface_origin, alpha_type, usage,
-      "TestLabel", initial_data);
+      "TestLabel", /*is_thread_safe=*/false, initial_data);
   EXPECT_FALSE(backing);
 }
 
diff --git a/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc
index 9c7b49d..7bba171 100644
--- a/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc
@@ -139,7 +139,7 @@
     uint32_t usage,
     std::string debug_label,
     bool is_thread_safe) {
-  DCHECK(!is_thread_safe);
+  CHECK(!is_thread_safe);
   return CreateSharedImageInternal(mailbox, format, surface_handle, size,
                                    color_space, surface_origin, alpha_type,
                                    usage, std::move(debug_label));
@@ -154,7 +154,9 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> pixel_data) {
+  CHECK(!is_thread_safe);
   SurfaceHandle surface_handle = SurfaceHandle();
   auto backing = CreateSharedImageInternal(
       mailbox, format, surface_handle, size, color_space, surface_origin,
diff --git a/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.h b/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.h
index 29f2dcf1..129768d 100644
--- a/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.h
@@ -51,6 +51,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
 
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
diff --git a/gpu/command_buffer/service/shared_image/raw_draw_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/raw_draw_image_backing_factory.cc
index 47bc61a6..9bd2948 100644
--- a/gpu/command_buffer/service/shared_image/raw_draw_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/raw_draw_image_backing_factory.cc
@@ -51,6 +51,7 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> data) {
   NOTREACHED() << "Not supported";
   return nullptr;
diff --git a/gpu/command_buffer/service/shared_image/raw_draw_image_backing_factory.h b/gpu/command_buffer/service/shared_image/raw_draw_image_backing_factory.h
index d307552..8c93325a 100644
--- a/gpu/command_buffer/service/shared_image/raw_draw_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/raw_draw_image_backing_factory.h
@@ -37,6 +37,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image/shared_image_backing_factory.h b/gpu/command_buffer/service/shared_image/shared_image_backing_factory.h
index 7b362bf..34a75fd9 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/shared_image_backing_factory.h
@@ -63,6 +63,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) = 0;
   virtual std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image/shared_image_factory.cc b/gpu/command_buffer/service/shared_image/shared_image_factory.cc
index 7a0a0be6..ffa6f1f7 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_factory.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_factory.cc
@@ -579,9 +579,9 @@
     return false;
   }
 
-  auto backing = factory->CreateSharedImage(mailbox, format, size, color_space,
-                                            surface_origin, alpha_type, usage,
-                                            std::move(debug_label), data);
+  auto backing = factory->CreateSharedImage(
+      mailbox, format, size, color_space, surface_origin, alpha_type, usage,
+      std::move(debug_label), IsSharedBetweenThreads(usage), data);
   if (backing) {
     DVLOG(1) << "CreateSharedImagePixels[" << backing->GetName()
              << "] with pixels size=" << size.ToString()
diff --git a/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.cc
index 155cef26..5dae579 100644
--- a/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.cc
@@ -47,6 +47,7 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> pixel_data) {
   NOTREACHED();
   return nullptr;
diff --git a/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.h b/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.h
index 4e5cb83..1d76aa8 100644
--- a/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.h
@@ -41,6 +41,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
 
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
diff --git a/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.cc
index 5a98863..b11f42d 100644
--- a/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.cc
@@ -106,15 +106,6 @@
     uint32_t usage,
     std::string debug_label,
     bool is_thread_safe) {
-  // Ensure that the backing is treated as thread safe only when DrDc is enabled
-  // for vulkan context.
-  // TODO(vikassoni): Wire |is_thread_safe| flag in remaining
-  // CreateSharedImage() factory methods also. Without this flag, backing will
-  // always be considered as thread safe when DrDc is enabled for vulkan mode
-  // even though it might be used on a single thread (RenderPass for example).
-  // That should be fine for now since we do not have/use any locks in backing.
-  DCHECK(!is_thread_safe ||
-         (context_state_->GrContextIsVulkan() && is_drdc_enabled_));
   if (use_graphite_) {
     auto backing = std::make_unique<WrappedGraphiteTextureBacking>(
         base::PassKey<WrappedSkImageBackingFactory>(), mailbox, format, size,
@@ -130,9 +121,7 @@
   auto backing = std::make_unique<WrappedSkImageBacking>(
       base::PassKey<WrappedSkImageBackingFactory>(), mailbox, format, size,
       color_space, surface_origin, alpha_type, usage, debug_label,
-      context_state_,
-      /*is_thread_safe=*/is_thread_safe &&
-          context_state_->GrContextIsVulkan() && is_drdc_enabled_);
+      context_state_, is_thread_safe);
   if (!backing->Initialize(debug_label)) {
     return nullptr;
   }
@@ -149,6 +138,7 @@
     SkAlphaType alpha_type,
     uint32_t usage,
     std::string debug_label,
+    bool is_thread_safe,
     base::span<const uint8_t> data) {
   if (use_graphite_) {
     auto backing = std::make_unique<WrappedGraphiteTextureBacking>(
@@ -165,9 +155,7 @@
   auto backing = std::make_unique<WrappedSkImageBacking>(
       base::PassKey<WrappedSkImageBackingFactory>(), mailbox, format, size,
       color_space, surface_origin, alpha_type, usage, debug_label,
-      context_state_,
-      /*is_thread_safe=*/context_state_->GrContextIsVulkan() &&
-          is_drdc_enabled_);
+      context_state_, is_thread_safe);
   if (!backing->InitializeWithData(debug_label, data)) {
     return nullptr;
   }
diff --git a/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.h b/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.h
index 15c6d69..c16aaaa 100644
--- a/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.h
@@ -50,6 +50,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override;
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
diff --git a/gpu/config/gpu_util.cc b/gpu/config/gpu_util.cc
index ac91886..6541c4b 100644
--- a/gpu/config/gpu_util.cc
+++ b/gpu/config/gpu_util.cc
@@ -266,11 +266,6 @@
                           base::FeatureList::OVERRIDE_DISABLE_FEATURE))
     return kGpuFeatureStatusDisabled;
 
-  // VDA video decoder on ChromeOS uses legacy mailboxes which is not compatible
-  // with OOP-C
-  if (!gpu_preferences.enable_chromeos_direct_video_decoder)
-    return kGpuFeatureStatusDisabled;
-
   // On certain ChromeOS devices, using Vulkan without OOP-C results in video
   // encode artifacts (b/318721705).
   if (gpu_preferences.use_vulkan != VulkanImplementationName::kNone)
diff --git a/gpu/ipc/common/mock_gpu_channel.h b/gpu/ipc/common/mock_gpu_channel.h
index eb4dd45..08474f5a0 100644
--- a/gpu/ipc/common/mock_gpu_channel.h
+++ b/gpu/ipc/common/mock_gpu_channel.h
@@ -84,6 +84,9 @@
                     int32_t,
                     int32_t,
                     WaitForGetOffsetInRangeCallback));
+  MOCK_METHOD5(
+      WaitForGetOffsetInRange,
+      bool(int32_t, uint32_t, int32_t, int32_t, CommandBuffer::State*));
 #if BUILDFLAG(IS_FUCHSIA)
   MOCK_METHOD5(RegisterSysmemBufferCollection,
                void(mojo::PlatformHandle,
diff --git a/gpu/ipc/service/image_decode_accelerator_stub_unittest.cc b/gpu/ipc/service/image_decode_accelerator_stub_unittest.cc
index 477f9d6..176e25d2 100644
--- a/gpu/ipc/service/image_decode_accelerator_stub_unittest.cc
+++ b/gpu/ipc/service/image_decode_accelerator_stub_unittest.cc
@@ -164,6 +164,7 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       std::string debug_label,
+      bool is_thread_safe,
       base::span<const uint8_t> pixel_data) override {
     NOTREACHED();
     return nullptr;
diff --git a/infra/config/generated/builder-owners/chrome-build-team@google.com.txt b/infra/config/generated/builder-owners/chrome-build-team@google.com.txt
index bc995ff..790f0e23 100644
--- a/infra/config/generated/builder-owners/chrome-build-team@google.com.txt
+++ b/infra/config/generated/builder-owners/chrome-build-team@google.com.txt
@@ -20,4 +20,6 @@
 ci/Deterministic Linux
 ci/Deterministic Linux (dbg)
 ci/linux-win-cross-rel
+try/linux-full-remote-rel
+try/linux-full-remote-rel-compilator
 try/linux-win-cross-rel
\ No newline at end of file
diff --git a/infra/config/generated/builders/build/android-build-perf-developer/properties.json b/infra/config/generated/builders/build/android-build-perf-developer/properties.json
index f7d3703a..ba3997f 100644
--- a/infra/config/generated/builders/build/android-build-perf-developer/properties.json
+++ b/infra/config/generated/builders/build/android-build-perf-developer/properties.json
@@ -54,7 +54,10 @@
     "scandeps_server": true
   },
   "$build/siso": {
-    "configs": [],
+    "configs": [
+      "remote-library-link",
+      "remote-exec-link"
+    ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
     "experiments": [],
diff --git a/infra/config/generated/builders/build/build-perf-android-siso/properties.json b/infra/config/generated/builders/build/build-perf-android-siso/properties.json
index 57d6e00a..40621df 100644
--- a/infra/config/generated/builders/build/build-perf-android-siso/properties.json
+++ b/infra/config/generated/builders/build/build-perf-android-siso/properties.json
@@ -58,7 +58,9 @@
   },
   "$build/siso": {
     "configs": [
-      "builder"
+      "builder",
+      "remote-library-link",
+      "remote-exec-link"
     ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
diff --git a/infra/config/generated/builders/build/build-perf-linux-siso/properties.json b/infra/config/generated/builders/build/build-perf-linux-siso/properties.json
index 62ebd9ed..c7429592 100644
--- a/infra/config/generated/builders/build/build-perf-linux-siso/properties.json
+++ b/infra/config/generated/builders/build/build-perf-linux-siso/properties.json
@@ -52,7 +52,9 @@
   },
   "$build/siso": {
     "configs": [
-      "builder"
+      "builder",
+      "remote-library-link",
+      "remote-exec-link"
     ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
diff --git a/infra/config/generated/builders/build/build-perf-windows-siso/properties.json b/infra/config/generated/builders/build/build-perf-windows-siso/properties.json
index 087b556..e83e93e6 100644
--- a/infra/config/generated/builders/build/build-perf-windows-siso/properties.json
+++ b/infra/config/generated/builders/build/build-perf-windows-siso/properties.json
@@ -52,7 +52,9 @@
   },
   "$build/siso": {
     "configs": [
-      "builder"
+      "builder",
+      "remote-library-link",
+      "remote-exec-link"
     ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
diff --git a/infra/config/generated/builders/build/ios-build-perf-developer/properties.json b/infra/config/generated/builders/build/ios-build-perf-developer/properties.json
index 82ef607..d85ea2f 100644
--- a/infra/config/generated/builders/build/ios-build-perf-developer/properties.json
+++ b/infra/config/generated/builders/build/ios-build-perf-developer/properties.json
@@ -51,7 +51,10 @@
     "scandeps_server": true
   },
   "$build/siso": {
-    "configs": [],
+    "configs": [
+      "remote-library-link",
+      "remote-exec-link"
+    ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
     "experiments": [],
diff --git a/infra/config/generated/builders/build/ios-build-perf-siso/properties.json b/infra/config/generated/builders/build/ios-build-perf-siso/properties.json
index 7a0fe84..48cd7d7d 100644
--- a/infra/config/generated/builders/build/ios-build-perf-siso/properties.json
+++ b/infra/config/generated/builders/build/ios-build-perf-siso/properties.json
@@ -55,7 +55,9 @@
   },
   "$build/siso": {
     "configs": [
-      "builder"
+      "builder",
+      "remote-library-link",
+      "remote-exec-link"
     ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
diff --git a/infra/config/generated/builders/build/linux-build-perf-developer/properties.json b/infra/config/generated/builders/build/linux-build-perf-developer/properties.json
index 84f28c3e..4e5a8f7 100644
--- a/infra/config/generated/builders/build/linux-build-perf-developer/properties.json
+++ b/infra/config/generated/builders/build/linux-build-perf-developer/properties.json
@@ -48,7 +48,10 @@
     "scandeps_server": true
   },
   "$build/siso": {
-    "configs": [],
+    "configs": [
+      "remote-library-link",
+      "remote-exec-link"
+    ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
     "experiments": [],
diff --git a/infra/config/generated/builders/build/linux-chromeos-build-perf-siso/properties.json b/infra/config/generated/builders/build/linux-chromeos-build-perf-siso/properties.json
index 313e541..68522486 100644
--- a/infra/config/generated/builders/build/linux-chromeos-build-perf-siso/properties.json
+++ b/infra/config/generated/builders/build/linux-chromeos-build-perf-siso/properties.json
@@ -53,7 +53,9 @@
   },
   "$build/siso": {
     "configs": [
-      "builder"
+      "builder",
+      "remote-library-link",
+      "remote-exec-link"
     ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
diff --git a/infra/config/generated/builders/build/mac-build-perf-developer/properties.json b/infra/config/generated/builders/build/mac-build-perf-developer/properties.json
index 79cc981..fc80cf67 100644
--- a/infra/config/generated/builders/build/mac-build-perf-developer/properties.json
+++ b/infra/config/generated/builders/build/mac-build-perf-developer/properties.json
@@ -48,7 +48,10 @@
     "scandeps_server": true
   },
   "$build/siso": {
-    "configs": [],
+    "configs": [
+      "remote-library-link",
+      "remote-exec-link"
+    ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
     "experiments": [],
diff --git a/infra/config/generated/builders/build/mac-build-perf-siso/properties.json b/infra/config/generated/builders/build/mac-build-perf-siso/properties.json
index 9d5604f..d0896ff 100644
--- a/infra/config/generated/builders/build/mac-build-perf-siso/properties.json
+++ b/infra/config/generated/builders/build/mac-build-perf-siso/properties.json
@@ -54,7 +54,9 @@
   },
   "$build/siso": {
     "configs": [
-      "builder"
+      "builder",
+      "remote-library-link",
+      "remote-exec-link"
     ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
diff --git a/infra/config/generated/builders/build/win-build-perf-developer/properties.json b/infra/config/generated/builders/build/win-build-perf-developer/properties.json
index 2827468d..de1f57b 100644
--- a/infra/config/generated/builders/build/win-build-perf-developer/properties.json
+++ b/infra/config/generated/builders/build/win-build-perf-developer/properties.json
@@ -48,7 +48,10 @@
     "scandeps_server": true
   },
   "$build/siso": {
-    "configs": [],
+    "configs": [
+      "remote-library-link",
+      "remote-exec-link"
+    ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
     "experiments": [],
diff --git a/infra/config/generated/builders/ci/GPU Linux Builder/properties.json b/infra/config/generated/builders/ci/GPU Linux Builder/properties.json
index 32a1ad83..64dc492 100644
--- a/infra/config/generated/builders/ci/GPU Linux Builder/properties.json
+++ b/infra/config/generated/builders/ci/GPU Linux Builder/properties.json
@@ -91,6 +91,10 @@
           "group": "tryserver.chromium.linux"
         },
         {
+          "builder": "linux-full-remote-rel",
+          "group": "tryserver.chromium.linux"
+        },
+        {
           "builder": "linux-mbi-mode-per-render-process-host-rel",
           "group": "tryserver.chromium.linux"
         },
diff --git a/infra/config/generated/builders/ci/Linux Builder/properties.json b/infra/config/generated/builders/ci/Linux Builder/properties.json
index d272b92..15efcd7 100644
--- a/infra/config/generated/builders/ci/Linux Builder/properties.json
+++ b/infra/config/generated/builders/ci/Linux Builder/properties.json
@@ -87,6 +87,10 @@
           "group": "tryserver.chromium.linux"
         },
         {
+          "builder": "linux-full-remote-rel",
+          "group": "tryserver.chromium.linux"
+        },
+        {
           "builder": "linux-mbi-mode-per-render-process-host-rel",
           "group": "tryserver.chromium.linux"
         },
diff --git "a/infra/config/generated/builders/ci/Linux Release \050NVIDIA\051/properties.json" "b/infra/config/generated/builders/ci/Linux Release \050NVIDIA\051/properties.json"
index 807237d0..adfb6c6 100644
--- "a/infra/config/generated/builders/ci/Linux Release \050NVIDIA\051/properties.json"
+++ "b/infra/config/generated/builders/ci/Linux Release \050NVIDIA\051/properties.json"
@@ -81,6 +81,10 @@
           "group": "tryserver.chromium.linux"
         },
         {
+          "builder": "linux-full-remote-rel",
+          "group": "tryserver.chromium.linux"
+        },
+        {
           "builder": "linux-mbi-mode-per-render-process-host-rel",
           "group": "tryserver.chromium.linux"
         },
diff --git a/infra/config/generated/builders/ci/Linux Tests/properties.json b/infra/config/generated/builders/ci/Linux Tests/properties.json
index b52a54d..ff4c393 100644
--- a/infra/config/generated/builders/ci/Linux Tests/properties.json
+++ b/infra/config/generated/builders/ci/Linux Tests/properties.json
@@ -77,6 +77,10 @@
           "group": "tryserver.chromium.linux"
         },
         {
+          "builder": "linux-full-remote-rel",
+          "group": "tryserver.chromium.linux"
+        },
+        {
           "builder": "linux-mbi-mode-per-render-process-host-rel",
           "group": "tryserver.chromium.linux"
         },
diff --git a/infra/config/generated/builders/ci/linux-win-cross-rel/targets/chromium.win.json b/infra/config/generated/builders/ci/linux-win-cross-rel/targets/chromium.win.json
index d6168b99..c7fbcd0 100644
--- a/infra/config/generated/builders/ci/linux-win-cross-rel/targets/chromium.win.json
+++ b/infra/config/generated/builders/ci/linux-win-cross-rel/targets/chromium.win.json
@@ -8,6 +8,21 @@
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "name": "absl_hardening_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
         "name": "base_unittests",
         "swarming": {
           "dimensions": {
@@ -18,6 +33,591 @@
         },
         "test": "base_unittests",
         "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "blink_common_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "blink_heap_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "boringssl_crypto_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "boringssl_ssl_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "capture_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "cast_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "components_browsertests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_browsertests",
+        "test_id_prefix": "ninja://components:components_browsertests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "components_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "content_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "crashpad_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crashpad_tests",
+        "test_id_prefix": "ninja://third_party/crashpad/crashpad:crashpad_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "crypto_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "env_chromium_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "env_chromium_unittests",
+        "test_id_prefix": "ninja://third_party/leveldatabase:env_chromium_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "events_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gcm_unit_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gcm_unit_tests",
+        "test_id_prefix": "ninja://google_apis/gcm:gcm_unit_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gin_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "google_apis_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gpu_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gwp_asan_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gwp_asan_unittests",
+        "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ipc_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "latency_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "leveldb_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "leveldb_unittests",
+        "test_id_prefix": "ninja://third_party/leveldatabase:leveldb_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "libjingle_xmpp_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "libjingle_xmpp_unittests",
+        "test_id_prefix": "ninja://third_party/libjingle_xmpp:libjingle_xmpp_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "liburlpattern_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "liburlpattern_unittests",
+        "test_id_prefix": "ninja://third_party/liburlpattern:liburlpattern_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "media_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "midi_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "midi_unittests",
+        "test_id_prefix": "ninja://media/midi:midi_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "mojo_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "net_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "perfetto_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "perfetto_unittests",
+        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "services_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "shell_dialogs_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "skia_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "sql_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "storage_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ui_base_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ui_touch_selection_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_touch_selection_unittests",
+        "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "url_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "webkit_unit_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "wtf_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "zlib_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "zlib_unittests",
+        "test_id_prefix": "ninja://third_party/zlib:zlib_unittests/"
       }
     ]
   }
diff --git a/infra/config/generated/builders/ci/linux-x64-cast-dbg/properties.json b/infra/config/generated/builders/ci/linux-x64-cast-dbg/properties.json
index 67b5366..b31bc60 100644
--- a/infra/config/generated/builders/ci/linux-x64-cast-dbg/properties.json
+++ b/infra/config/generated/builders/ci/linux-x64-cast-dbg/properties.json
@@ -69,8 +69,5 @@
     ]
   },
   "builder_group": "chromium.linux",
-  "recipe": "chromium",
-  "sheriff_rotations": [
-    "chromium"
-  ]
+  "recipe": "chromium"
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/gn_args_locations.json b/infra/config/generated/builders/gn_args_locations.json
index 353d7f3..2e8ac24 100644
--- a/infra/config/generated/builders/gn_args_locations.json
+++ b/infra/config/generated/builders/gn_args_locations.json
@@ -775,6 +775,7 @@
     "linux-exp-msan-fyi-rel": "try/linux-exp-msan-fyi-rel/gn-args.json",
     "linux-extended-tracing-rel": "try/linux-extended-tracing-rel/gn-args.json",
     "linux-fieldtrial-rel": "try/linux-fieldtrial-rel/gn-args.json",
+    "linux-full-remote-rel": "try/linux-full-remote-rel/gn-args.json",
     "linux-gcc-rel": "try/linux-gcc-rel/gn-args.json",
     "linux-headless-shell-rel": "try/linux-headless-shell-rel/gn-args.json",
     "linux-js-code-coverage": "try/linux-js-code-coverage/gn-args.json",
diff --git a/infra/config/generated/builders/try/fuchsia-x64-cast-receiver-rel/properties.json b/infra/config/generated/builders/try/fuchsia-x64-cast-receiver-rel/properties.json
index 93a2205..59699c4 100644
--- a/infra/config/generated/builders/try/fuchsia-x64-cast-receiver-rel/properties.json
+++ b/infra/config/generated/builders/try/fuchsia-x64-cast-receiver-rel/properties.json
@@ -45,7 +45,8 @@
           "builder": "fuchsia-x64-cast-receiver-rel",
           "project": "chromium"
         }
-      ]
+      ],
+      "retry_without_patch": false
     }
   },
   "$build/code_coverage": {
diff --git a/infra/config/generated/builders/try/linux-full-remote-rel-compilator/properties.json b/infra/config/generated/builders/try/linux-full-remote-rel-compilator/properties.json
new file mode 100644
index 0000000..8046824
--- /dev/null
+++ b/infra/config/generated/builders/try/linux-full-remote-rel-compilator/properties.json
@@ -0,0 +1,4 @@
+{
+  "builder_group": "tryserver.chromium.linux",
+  "recipe": "chromium/compilator"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/linux-full-remote-rel/gn-args.json b/infra/config/generated/builders/try/linux-full-remote-rel/gn-args.json
new file mode 100644
index 0000000..4bb3faa
--- /dev/null
+++ b/infra/config/generated/builders/try/linux-full-remote-rel/gn-args.json
@@ -0,0 +1,15 @@
+{
+  "gn_args": {
+    "coverage_instrumentation_input_file": "//.code-coverage/files_to_instrument.txt",
+    "dcheck_always_on": true,
+    "devtools_skip_typecheck": false,
+    "ffmpeg_branding": "Chrome",
+    "is_component_build": false,
+    "is_debug": false,
+    "proprietary_codecs": true,
+    "symbol_level": 0,
+    "use_clang_coverage": true,
+    "use_remoteexec": true,
+    "use_siso": true
+  }
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/linux-full-remote-rel/properties.json b/infra/config/generated/builders/try/linux-full-remote-rel/properties.json
new file mode 100644
index 0000000..b317759
--- /dev/null
+++ b/infra/config/generated/builders/try/linux-full-remote-rel/properties.json
@@ -0,0 +1,193 @@
+{
+  "$build/chromium_orchestrator": {
+    "compilator": "linux-full-remote-rel-compilator",
+    "compilator_watcher_git_revision": "27c191f304c8d7329a393d8a69020fc14032c3c3"
+  },
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "additional_exclusions": [
+        "infra/config/generated/builders/try/linux-full-remote-rel/gn-args.json"
+      ],
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "GPU Linux Builder",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-gpu-archive",
+              "builder_group": "chromium.gpu",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "linux"
+              },
+              "legacy_gclient_config": {
+                "apply_configs": [
+                  "use_clang_coverage"
+                ],
+                "config": "chromium"
+              }
+            }
+          },
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "Linux Builder",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-linux-archive",
+              "builder_group": "chromium.linux",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "linux"
+              },
+              "legacy_gclient_config": {
+                "apply_configs": [
+                  "use_clang_coverage"
+                ],
+                "config": "chromium"
+              }
+            }
+          },
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "Linux Release (NVIDIA)",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-gpu-archive",
+              "builder_group": "chromium.gpu",
+              "execution_mode": "TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "linux"
+              },
+              "legacy_gclient_config": {
+                "apply_configs": [
+                  "use_clang_coverage"
+                ],
+                "config": "chromium"
+              },
+              "parent": {
+                "bucket": "ci",
+                "builder": "GPU Linux Builder",
+                "project": "chromium"
+              }
+            }
+          },
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "Linux Tests",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-linux-archive",
+              "builder_group": "chromium.linux",
+              "execution_mode": "TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "linux"
+              },
+              "legacy_gclient_config": {
+                "apply_configs": [
+                  "use_clang_coverage"
+                ],
+                "config": "chromium"
+              },
+              "parent": {
+                "bucket": "ci",
+                "builder": "Linux Builder",
+                "project": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "GPU Linux Builder",
+          "project": "chromium"
+        },
+        {
+          "bucket": "ci",
+          "builder": "Linux Builder",
+          "project": "chromium"
+        }
+      ],
+      "builder_ids_in_scope_for_testing": [
+        {
+          "bucket": "ci",
+          "builder": "Linux Release (NVIDIA)",
+          "project": "chromium"
+        },
+        {
+          "bucket": "ci",
+          "builder": "Linux Tests",
+          "project": "chromium"
+        }
+      ]
+    }
+  },
+  "$build/code_coverage": {
+    "use_clang_coverage": true
+  },
+  "$build/flakiness": {
+    "check_for_flakiness": true,
+    "check_for_flakiness_with_resultdb": true
+  },
+  "$build/reclient": {
+    "instance": "rbe-chromium-untrusted",
+    "jobs": 500,
+    "metrics_project": "chromium-reclient-metrics",
+    "scandeps_server": true
+  },
+  "$build/siso": {
+    "configs": [
+      "builder",
+      "remote-library-link",
+      "remote-exec-link"
+    ],
+    "enable_cloud_profiler": true,
+    "enable_cloud_trace": true,
+    "experiments": [],
+    "project": "rbe-chromium-untrusted"
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "tryserver.chromium.linux",
+  "cq": "required",
+  "recipe": "chromium/orchestrator"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/linux-win-cross-rel/targets/chromium.win.json b/infra/config/generated/builders/try/linux-win-cross-rel/targets/chromium.win.json
index d6168b99..c7fbcd0 100644
--- a/infra/config/generated/builders/try/linux-win-cross-rel/targets/chromium.win.json
+++ b/infra/config/generated/builders/try/linux-win-cross-rel/targets/chromium.win.json
@@ -8,6 +8,21 @@
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "name": "absl_hardening_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
         "name": "base_unittests",
         "swarming": {
           "dimensions": {
@@ -18,6 +33,591 @@
         },
         "test": "base_unittests",
         "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "blink_common_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "blink_heap_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "boringssl_crypto_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "boringssl_ssl_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "capture_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "cast_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "components_browsertests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_browsertests",
+        "test_id_prefix": "ninja://components:components_browsertests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "components_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "content_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "crashpad_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crashpad_tests",
+        "test_id_prefix": "ninja://third_party/crashpad/crashpad:crashpad_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "crypto_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "env_chromium_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "env_chromium_unittests",
+        "test_id_prefix": "ninja://third_party/leveldatabase:env_chromium_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "events_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gcm_unit_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gcm_unit_tests",
+        "test_id_prefix": "ninja://google_apis/gcm:gcm_unit_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gin_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "google_apis_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gpu_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gwp_asan_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gwp_asan_unittests",
+        "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ipc_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "latency_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "leveldb_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "leveldb_unittests",
+        "test_id_prefix": "ninja://third_party/leveldatabase:leveldb_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "libjingle_xmpp_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "libjingle_xmpp_unittests",
+        "test_id_prefix": "ninja://third_party/libjingle_xmpp:libjingle_xmpp_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "liburlpattern_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "liburlpattern_unittests",
+        "test_id_prefix": "ninja://third_party/liburlpattern:liburlpattern_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "media_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "midi_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "midi_unittests",
+        "test_id_prefix": "ninja://media/midi:midi_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "mojo_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "net_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "perfetto_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "perfetto_unittests",
+        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "services_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "shell_dialogs_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "skia_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "sql_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "storage_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ui_base_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ui_touch_selection_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_touch_selection_unittests",
+        "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "url_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "webkit_unit_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "wtf_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "zlib_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Windows-10-19045"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "zlib_unittests",
+        "test_id_prefix": "ninja://third_party/zlib:zlib_unittests/"
       }
     ]
   }
diff --git a/infra/config/generated/builders/try/win_optional_gpu_tests_rel/gn-args.json b/infra/config/generated/builders/try/win_optional_gpu_tests_rel/gn-args.json
index 40d5c10..b365922 100644
--- a/infra/config/generated/builders/try/win_optional_gpu_tests_rel/gn-args.json
+++ b/infra/config/generated/builders/try/win_optional_gpu_tests_rel/gn-args.json
@@ -6,6 +6,7 @@
     "is_debug": false,
     "proprietary_codecs": true,
     "symbol_level": 1,
-    "use_remoteexec": true
+    "use_remoteexec": true,
+    "use_siso": true
   }
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/win_optional_gpu_tests_rel/properties.json b/infra/config/generated/builders/try/win_optional_gpu_tests_rel/properties.json
index 6e5c777..8a01bb3 100644
--- a/infra/config/generated/builders/try/win_optional_gpu_tests_rel/properties.json
+++ b/infra/config/generated/builders/try/win_optional_gpu_tests_rel/properties.json
@@ -55,6 +55,15 @@
     "metrics_project": "chromium-reclient-metrics",
     "scandeps_server": true
   },
+  "$build/siso": {
+    "configs": [
+      "builder"
+    ],
+    "enable_cloud_profiler": true,
+    "enable_cloud_trace": true,
+    "experiments": [],
+    "project": "rbe-chromium-untrusted"
+  },
   "$recipe_engine/resultdb/test_presentation": {
     "column_keys": [],
     "grouping_keys": [
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md
index 6222b6e..272d718 100644
--- a/infra/config/generated/cq-builders.md
+++ b/infra/config/generated/cq-builders.md
@@ -643,6 +643,9 @@
   * [`//tools/clang/scripts/update.py`](https://cs.chromium.org/search?q=+file:tools/clang/scripts/update.py)
   * [`//ui/gl/features.gni`](https://cs.chromium.org/search?q=+file:ui/gl/features.gni)
 
+* [linux-full-remote-rel](https://ci.chromium.org/p/chromium/builders/try/linux-full-remote-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""linux-full-remote-rel""))
+  * Experiment percentage: 10.0
+
 * [mac13-arm64-rel](https://ci.chromium.org/p/chromium/builders/try/mac13-arm64-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""mac13-arm64-rel""))
   * Experiment percentage: 100.0
 
diff --git a/infra/config/generated/cq-usage/mega_cq_bots.txt b/infra/config/generated/cq-usage/mega_cq_bots.txt
index da68cb7..727819ba 100644
--- a/infra/config/generated/cq-usage/mega_cq_bots.txt
+++ b/infra/config/generated/cq-usage/mega_cq_bots.txt
@@ -95,6 +95,7 @@
 chromium/try/linux-dawn-rel
 chromium/try/linux-dcheck-off-rel
 chromium/try/linux-extended-tracing-rel
+chromium/try/linux-full-remote-rel
 chromium/try/linux-gcc-rel
 chromium/try/linux-lacros-asan-lsan-rel
 chromium/try/linux-lacros-dbg
@@ -112,7 +113,6 @@
 chromium/try/linux-wayland-rel
 chromium/try/linux-webkit-asan-rel
 chromium/try/linux-webkit-msan-rel
-chromium/try/linux-x64-cast-dbg
 chromium/try/linux_chromium_archive_rel_ng
 chromium/try/linux_chromium_asan_rel_ng
 chromium/try/linux_chromium_cfi_rel_ng
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 0b7222c2..dde30333 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -3355,6 +3355,33 @@
         includable_only: true
       }
       builders {
+        name: "chromium/try/linux-full-remote-rel"
+        experiment_percentage: 10
+        location_filters {
+          gerrit_host_regexp: ".*"
+          gerrit_project_regexp: ".*"
+          path_regexp: "docs/.+"
+          exclude: true
+        }
+        location_filters {
+          gerrit_host_regexp: ".*"
+          gerrit_project_regexp: ".*"
+          path_regexp: "infra/config/.+"
+          exclude: true
+        }
+        location_filters {
+          gerrit_host_regexp: ".*"
+          gerrit_project_regexp: ".*"
+          path_regexp: "infra/config/generated/builders/try/linux-full-remote-rel/.+"
+        }
+        mode_allowlist: "DRY_RUN"
+        mode_allowlist: "FULL_RUN"
+      }
+      builders {
+        name: "chromium/try/linux-full-remote-rel-compilator"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/linux-gcc-rel"
         includable_only: true
       }
@@ -5294,7 +5321,7 @@
     name: "default-limit"
     run {
       max_active {
-        value: 25
+        value: 10
       }
     }
   }
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 8b9d0ab1..05019d7 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -71,7 +71,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -159,7 +159,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -245,7 +245,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -328,7 +328,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -411,7 +411,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -494,7 +494,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -577,7 +577,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -660,7 +660,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -743,7 +743,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -831,7 +831,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -919,7 +919,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1007,7 +1007,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1090,7 +1090,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1173,7 +1173,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1256,7 +1256,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1340,7 +1340,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1424,7 +1424,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1508,7 +1508,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1591,7 +1591,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1694,7 +1694,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1774,7 +1774,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1854,7 +1854,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -1950,7 +1950,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -2046,7 +2046,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -2142,7 +2142,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -2237,7 +2237,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -2333,7 +2333,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -2429,7 +2429,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -2519,7 +2519,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -2615,7 +2615,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -2710,7 +2710,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -2805,7 +2805,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -2900,7 +2900,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -2995,7 +2995,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -3090,7 +3090,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -3185,7 +3185,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -3280,7 +3280,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -3374,7 +3374,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -3468,7 +3468,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -3564,7 +3564,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -3660,7 +3660,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -3756,7 +3756,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -3852,7 +3852,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -3948,7 +3948,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -4041,7 +4041,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -4135,7 +4135,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -4232,7 +4232,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -4328,7 +4328,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -4419,7 +4419,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -4512,7 +4512,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -4606,7 +4606,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -4701,7 +4701,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -4786,7 +4786,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -4890,7 +4890,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -4986,7 +4986,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -5082,7 +5082,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -5173,7 +5173,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -5277,7 +5277,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -5390,7 +5390,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -5502,7 +5502,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -5606,7 +5606,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -5710,7 +5710,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -5813,7 +5813,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -5917,7 +5917,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -6029,7 +6029,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -6142,7 +6142,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -6245,7 +6245,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -6357,7 +6357,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -6473,7 +6473,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -6580,7 +6580,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -6688,7 +6688,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -6782,7 +6782,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -6877,7 +6877,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -6973,7 +6973,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -7068,7 +7068,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -7163,7 +7163,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -7258,7 +7258,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -7353,7 +7353,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -7449,7 +7449,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -7545,7 +7545,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -7640,7 +7640,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -7735,7 +7735,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -7830,7 +7830,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -7925,7 +7925,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -8021,7 +8021,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -8117,7 +8117,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -8213,7 +8213,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -8308,7 +8308,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -8403,7 +8403,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -8498,7 +8498,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -8593,7 +8593,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -8688,7 +8688,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -8782,7 +8782,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -8876,7 +8876,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -8971,7 +8971,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -9066,7 +9066,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -9161,7 +9161,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -9255,7 +9255,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -9349,7 +9349,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -9444,7 +9444,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -9539,7 +9539,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -9634,7 +9634,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -9729,7 +9729,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -9824,7 +9824,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -9919,7 +9919,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -10015,7 +10015,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -10110,7 +10110,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -10205,7 +10205,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -10301,7 +10301,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -10397,7 +10397,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -10492,7 +10492,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -10587,7 +10587,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -10682,7 +10682,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -10777,7 +10777,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -10872,7 +10872,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -10967,7 +10967,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -11063,7 +11063,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -11159,7 +11159,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -11254,7 +11254,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -11349,7 +11349,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -11444,7 +11444,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -11539,7 +11539,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -11634,7 +11634,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -11729,7 +11729,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -11827,7 +11827,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -11943,7 +11943,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -12057,7 +12057,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -12169,7 +12169,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -12281,7 +12281,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -12393,7 +12393,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -12489,7 +12489,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -12585,7 +12585,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -12681,7 +12681,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -12777,7 +12777,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -12871,7 +12871,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -12965,7 +12965,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -13059,7 +13059,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -13153,7 +13153,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -13249,7 +13249,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -13345,7 +13345,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -13441,7 +13441,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -13537,7 +13537,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -13633,7 +13633,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -13729,7 +13729,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -13825,7 +13825,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -13920,7 +13920,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -14015,7 +14015,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -14033,7 +14033,7 @@
           use_invocation_timestamp: true
         }
       }
-      description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/gpu-try-linux-nvidia-rel\">gpu-try-linux-nvidia-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-dcheck-off-rel\">linux-dcheck-off-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-mbi-mode-per-render-process-host-rel\">linux-mbi-mode-per-render-process-host-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-rel\">linux-rel</a></li></ul>"
+      description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/gpu-try-linux-nvidia-rel\">gpu-try-linux-nvidia-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-dcheck-off-rel\">linux-dcheck-off-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-full-remote-rel\">linux-full-remote-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-mbi-mode-per-render-process-host-rel\">linux-mbi-mode-per-render-process-host-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-rel\">linux-rel</a></li></ul>"
       shadow_builder_adjustments {
         service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
         pool: "luci.chromium.try"
@@ -14108,7 +14108,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -14202,7 +14202,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -14293,7 +14293,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -14389,7 +14389,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -14482,7 +14482,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -14577,7 +14577,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -14672,7 +14672,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -14765,7 +14765,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -14852,7 +14852,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -14948,7 +14948,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -15044,7 +15044,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -15140,7 +15140,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -15234,7 +15234,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -15331,7 +15331,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -15427,7 +15427,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -15523,7 +15523,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -15619,7 +15619,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -15715,7 +15715,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -15809,7 +15809,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -15904,7 +15904,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -16004,7 +16004,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -16109,7 +16109,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -16204,7 +16204,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -16300,7 +16300,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -16318,7 +16318,7 @@
           use_invocation_timestamp: true
         }
       }
-      description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-dcheck-off-rel\">linux-dcheck-off-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-mbi-mode-per-render-process-host-rel\">linux-mbi-mode-per-render-process-host-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-rel\">linux-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux_chromium_compile_rel_ng\">linux_chromium_compile_rel_ng</a></li></ul>"
+      description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-dcheck-off-rel\">linux-dcheck-off-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-full-remote-rel\">linux-full-remote-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-mbi-mode-per-render-process-host-rel\">linux-mbi-mode-per-render-process-host-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-rel\">linux-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux_chromium_compile_rel_ng\">linux_chromium_compile_rel_ng</a></li></ul>"
       shadow_builder_adjustments {
         service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
         pool: "luci.chromium.try"
@@ -16396,7 +16396,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -16492,7 +16492,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -16585,7 +16585,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -16679,7 +16679,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -16775,7 +16775,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -16870,7 +16870,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -16966,7 +16966,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -17061,7 +17061,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -17153,7 +17153,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -17248,7 +17248,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -17343,7 +17343,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -17438,7 +17438,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -17534,7 +17534,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -17629,7 +17629,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -17724,7 +17724,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -17819,7 +17819,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -17914,7 +17914,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -18010,7 +18010,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -18106,7 +18106,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -18201,7 +18201,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -18219,7 +18219,7 @@
           use_invocation_timestamp: true
         }
       }
-      description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/gpu-try-linux-nvidia-rel\">gpu-try-linux-nvidia-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-dcheck-off-rel\">linux-dcheck-off-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-mbi-mode-per-render-process-host-rel\">linux-mbi-mode-per-render-process-host-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-rel\">linux-rel</a></li></ul>"
+      description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/gpu-try-linux-nvidia-rel\">gpu-try-linux-nvidia-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-dcheck-off-rel\">linux-dcheck-off-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-full-remote-rel\">linux-full-remote-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-mbi-mode-per-render-process-host-rel\">linux-mbi-mode-per-render-process-host-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-rel\">linux-rel</a></li></ul>"
       shadow_builder_adjustments {
         service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
         pool: "luci.chromium.try"
@@ -18297,7 +18297,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -18393,7 +18393,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -18489,7 +18489,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -18517,7 +18517,7 @@
           use_invocation_timestamp: true
         }
       }
-      description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-dcheck-off-rel\">linux-dcheck-off-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-mbi-mode-per-render-process-host-rel\">linux-mbi-mode-per-render-process-host-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-rel\">linux-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux_chromium_compile_rel_ng\">linux_chromium_compile_rel_ng</a></li></ul>"
+      description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-dcheck-off-rel\">linux-dcheck-off-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-full-remote-rel\">linux-full-remote-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-mbi-mode-per-render-process-host-rel\">linux-mbi-mode-per-render-process-host-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-rel\">linux-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/linux_chromium_compile_rel_ng\">linux_chromium_compile_rel_ng</a></li></ul>"
       shadow_builder_adjustments {
         service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
         pool: "luci.chromium.try"
@@ -18595,7 +18595,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -18691,7 +18691,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -18785,7 +18785,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -18880,7 +18880,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -18976,7 +18976,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -19070,7 +19070,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -19165,7 +19165,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -19258,7 +19258,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -19352,7 +19352,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -19446,7 +19446,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -19540,7 +19540,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -19632,7 +19632,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -19722,7 +19722,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -19814,7 +19814,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -19909,7 +19909,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -20004,7 +20004,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -20099,7 +20099,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -20194,7 +20194,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -20289,7 +20289,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -20384,7 +20384,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -20479,7 +20479,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -20574,7 +20574,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -20669,7 +20669,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -20764,7 +20764,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -20859,7 +20859,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -20954,7 +20954,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -21049,7 +21049,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -21144,7 +21144,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -21239,7 +21239,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -21334,7 +21334,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -21429,7 +21429,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -21524,7 +21524,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -21607,7 +21607,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -21698,7 +21698,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -21802,7 +21802,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -21898,7 +21898,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -21994,7 +21994,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -22090,7 +22090,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -22182,7 +22182,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -22277,7 +22277,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -22370,7 +22370,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -22463,7 +22463,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -22557,7 +22557,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -22653,7 +22653,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -22748,7 +22748,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -22842,7 +22842,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -22936,7 +22936,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -23030,7 +23030,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -23124,7 +23124,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -23218,7 +23218,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -23312,7 +23312,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -23406,7 +23406,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -23500,7 +23500,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -23594,7 +23594,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -23688,7 +23688,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -23782,7 +23782,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -23876,7 +23876,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -23970,7 +23970,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -24064,7 +24064,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -24156,7 +24156,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -24239,7 +24239,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -24333,7 +24333,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -24427,7 +24427,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -24521,7 +24521,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -24615,7 +24615,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -24709,7 +24709,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -24804,7 +24804,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -24899,7 +24899,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -24994,7 +24994,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -25089,7 +25089,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -25173,7 +25173,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -25268,7 +25268,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -25363,7 +25363,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -25455,7 +25455,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -25550,7 +25550,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -25644,7 +25644,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -25738,7 +25738,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -25832,7 +25832,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -25926,7 +25926,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -26018,7 +26018,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -26110,7 +26110,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -26192,7 +26192,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -26288,7 +26288,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -26385,7 +26385,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -26481,7 +26481,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -26577,7 +26577,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -26673,7 +26673,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -26769,7 +26769,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -26865,7 +26865,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -26959,7 +26959,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -27052,7 +27052,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -27146,7 +27146,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -27241,7 +27241,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -27336,7 +27336,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -27431,7 +27431,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -27526,7 +27526,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -27623,7 +27623,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -27716,7 +27716,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -27808,7 +27808,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -27901,7 +27901,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -27996,7 +27996,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -28091,7 +28091,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -28186,7 +28186,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -28281,7 +28281,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -28376,7 +28376,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -28471,7 +28471,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -28566,7 +28566,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -28661,7 +28661,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -28756,7 +28756,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -28850,7 +28850,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -28944,7 +28944,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -29038,7 +29038,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -29133,7 +29133,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -29228,7 +29228,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -29323,7 +29323,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -29418,7 +29418,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -29507,7 +29507,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -29615,7 +29615,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -29709,7 +29709,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -29800,7 +29800,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -29893,7 +29893,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -29986,7 +29986,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -30080,7 +30080,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -30176,7 +30176,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -30270,7 +30270,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -30362,7 +30362,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -30453,7 +30453,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -30545,7 +30545,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -30627,7 +30627,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -30808,7 +30808,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -30983,7 +30983,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -31077,7 +31077,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -31171,7 +31171,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -31267,7 +31267,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -31358,7 +31358,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -31450,7 +31450,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -31555,7 +31555,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -31649,7 +31649,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -31746,7 +31746,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -31860,7 +31860,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -31953,7 +31953,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -32046,7 +32046,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -32139,7 +32139,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -32231,7 +32231,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -32324,7 +32324,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -32417,7 +32417,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -32510,7 +32510,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -32603,7 +32603,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -32697,7 +32697,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -32790,7 +32790,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -32883,7 +32883,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -32976,7 +32976,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -33069,7 +33069,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -33162,7 +33162,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -33255,7 +33255,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -33348,7 +33348,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -33439,7 +33439,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -33551,7 +33551,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -33644,7 +33644,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -33737,7 +33737,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -33830,7 +33830,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -33923,7 +33923,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -34016,7 +34016,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -34109,7 +34109,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -34202,7 +34202,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -34295,7 +34295,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -34388,7 +34388,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -34481,7 +34481,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -34574,7 +34574,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -34667,7 +34667,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -34760,7 +34760,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -34853,7 +34853,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -34946,7 +34946,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -35063,7 +35063,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -35153,7 +35153,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -35247,7 +35247,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -35341,7 +35341,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -35438,7 +35438,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -35532,7 +35532,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -35627,7 +35627,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -35723,7 +35723,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -35817,7 +35817,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -35911,7 +35911,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -36004,7 +36004,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -36096,7 +36096,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -36188,7 +36188,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -36378,7 +36378,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -36472,7 +36472,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -36566,7 +36566,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -36659,7 +36659,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -36754,7 +36754,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -36848,7 +36848,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -36941,7 +36941,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -37035,7 +37035,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -37126,7 +37126,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -37229,7 +37229,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -37324,7 +37324,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -37420,7 +37420,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -37516,7 +37516,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -37612,7 +37612,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -37708,7 +37708,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -37804,7 +37804,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -37900,7 +37900,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -37996,7 +37996,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -38092,7 +38092,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -38188,7 +38188,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -38284,7 +38284,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -38378,7 +38378,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -38473,7 +38473,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -38615,7 +38615,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -38720,7 +38720,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -38814,7 +38814,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -38909,7 +38909,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -39005,7 +39005,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -39101,7 +39101,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -39197,7 +39197,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -39290,7 +39290,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -39385,7 +39385,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -39481,7 +39481,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -39668,7 +39668,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -39845,7 +39845,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -39942,7 +39942,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -40039,7 +40039,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -40136,7 +40136,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -40234,7 +40234,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -40330,7 +40330,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -40424,7 +40424,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -40523,7 +40523,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -40620,7 +40620,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -40717,7 +40717,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -40811,7 +40811,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -40909,7 +40909,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -41003,7 +41003,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -41097,7 +41097,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -41192,7 +41192,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -41287,7 +41287,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -41381,7 +41381,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -41476,7 +41476,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -41571,7 +41571,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -41667,7 +41667,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -41763,7 +41763,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -41859,7 +41859,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -41955,7 +41955,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -42051,7 +42051,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -42146,7 +42146,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -42242,7 +42242,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -42335,7 +42335,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -42426,7 +42426,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -42521,7 +42521,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -42617,7 +42617,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -42693,7 +42693,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -42911,7 +42911,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -43084,7 +43084,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -43248,7 +43248,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -43342,7 +43342,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -43437,7 +43437,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -43530,7 +43530,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -43626,7 +43626,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -43780,7 +43780,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -43893,7 +43893,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -43987,7 +43987,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -44079,7 +44079,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -44172,7 +44172,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -44266,7 +44266,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -44360,7 +44360,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -44455,7 +44455,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -44547,7 +44547,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -44642,7 +44642,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -44736,7 +44736,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -44830,7 +44830,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -44925,7 +44925,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -45009,7 +45009,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -45112,7 +45112,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -45207,7 +45207,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -45301,7 +45301,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -45386,7 +45386,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -45489,7 +45489,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -45583,7 +45583,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -45676,7 +45676,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -45768,7 +45768,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -45862,7 +45862,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -45956,7 +45956,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -46049,7 +46049,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -46144,7 +46144,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -46236,7 +46236,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -46329,7 +46329,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -46424,7 +46424,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -46515,7 +46515,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -46607,7 +46607,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -46700,7 +46700,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -46795,7 +46795,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -46889,7 +46889,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -46978,7 +46978,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -47072,7 +47072,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -47157,7 +47157,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -47252,7 +47252,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -47344,7 +47344,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -47470,7 +47470,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -47572,7 +47572,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -47664,7 +47664,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -47759,7 +47759,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -47855,7 +47855,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -47995,7 +47995,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -48100,7 +48100,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -48196,7 +48196,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -48290,7 +48290,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -48385,7 +48385,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -48478,7 +48478,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -48570,7 +48570,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -48661,7 +48661,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -48752,7 +48752,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -48845,7 +48845,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -48939,7 +48939,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -49033,7 +49033,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -49126,7 +49126,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -49221,7 +49221,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -49315,7 +49315,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -49408,7 +49408,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -49502,7 +49502,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -49567,10 +49567,7 @@
         '  },'
         '  "builder_group": "chromium.linux",'
         '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium",'
-        '  "sheriff_rotations": ['
-        '    "chromium"'
-        '  ]'
+        '  "recipe": "chromium"'
         '}'
       execution_timeout_secs: 10800
       build_numbers: YES
@@ -49597,7 +49594,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -49763,7 +49760,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -49982,7 +49979,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -50157,7 +50154,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -50251,7 +50248,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -50346,7 +50343,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -50440,7 +50437,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -50533,7 +50530,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -50627,7 +50624,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -50722,7 +50719,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -50814,7 +50811,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -50903,7 +50900,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -50995,7 +50992,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -51087,7 +51084,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -51179,7 +51176,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -51272,7 +51269,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -51365,7 +51362,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -51457,7 +51454,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -51550,7 +51547,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -51642,7 +51639,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -51732,7 +51729,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -51821,7 +51818,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -51910,7 +51907,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -51999,7 +51996,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -52089,7 +52086,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -52180,7 +52177,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -52270,7 +52267,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -52361,7 +52358,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -52451,7 +52448,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -52547,7 +52544,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -52639,7 +52636,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -52729,7 +52726,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -52818,7 +52815,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -52909,7 +52906,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -53001,7 +52998,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -53091,7 +53088,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -53185,7 +53182,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -53277,7 +53274,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -53366,7 +53363,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -53457,7 +53454,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -53549,7 +53546,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -53643,7 +53640,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -53735,7 +53732,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -53824,7 +53821,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -53915,7 +53912,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -54007,7 +54004,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -54083,7 +54080,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -54158,7 +54155,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -54233,7 +54230,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -54450,7 +54447,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -54599,7 +54596,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -54702,7 +54699,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -54795,7 +54792,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -54892,7 +54889,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -54986,7 +54983,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -55083,7 +55080,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -55175,7 +55172,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -55268,7 +55265,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -55360,7 +55357,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -55455,7 +55452,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -55540,7 +55537,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -55635,7 +55632,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -55728,7 +55725,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -55820,7 +55817,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -55915,7 +55912,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -56055,7 +56052,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -56204,7 +56201,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -56309,7 +56306,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -56405,7 +56402,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -56498,7 +56495,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -56590,7 +56587,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -56683,7 +56680,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -56773,7 +56770,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -56863,7 +56860,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -57025,7 +57022,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -57189,7 +57186,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -57283,7 +57280,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -57377,7 +57374,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -57469,7 +57466,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -57560,7 +57557,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -57651,7 +57648,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -57741,7 +57738,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -57832,7 +57829,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -57925,7 +57922,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -58016,7 +58013,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -58111,7 +58108,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -58203,7 +58200,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -58293,7 +58290,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -58385,7 +58382,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -58478,7 +58475,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -58571,7 +58568,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -58664,7 +58661,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -58758,7 +58755,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -58849,7 +58846,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -58956,7 +58953,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -59041,7 +59038,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -59126,7 +59123,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -59210,7 +59207,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -59295,7 +59292,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -59376,7 +59373,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -59460,7 +59457,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -59545,7 +59542,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -59629,7 +59626,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -59926,7 +59923,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60008,7 +60005,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60115,7 +60112,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60216,7 +60213,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60301,7 +60298,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60386,7 +60383,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60471,7 +60468,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60556,7 +60553,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60641,7 +60638,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60726,7 +60723,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60811,7 +60808,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60896,7 +60893,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -60981,7 +60978,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61066,7 +61063,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61150,7 +61147,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61234,7 +61231,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61319,7 +61316,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61404,7 +61401,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61489,7 +61486,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61574,7 +61571,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61659,7 +61656,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61744,7 +61741,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61829,7 +61826,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61914,7 +61911,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -61999,7 +61996,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -62080,7 +62077,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -62169,7 +62166,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -62258,7 +62255,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -62347,7 +62344,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -62436,7 +62433,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -62521,7 +62518,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -62606,7 +62603,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -62690,7 +62687,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -62774,7 +62771,7 @@
           table: "gpu_ci_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -63488,7 +63485,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -63572,7 +63569,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -63657,7 +63654,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -63753,7 +63750,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -63848,7 +63845,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -63943,7 +63940,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -64038,7 +64035,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -64134,7 +64131,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -64229,7 +64226,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -64324,7 +64321,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -64509,7 +64506,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -64604,7 +64601,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -64699,7 +64696,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -64806,7 +64803,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -64901,7 +64898,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -64996,7 +64993,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -65091,7 +65088,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -65207,7 +65204,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -65301,7 +65298,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -65396,7 +65393,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -65484,7 +65481,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -65578,7 +65575,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -65674,7 +65671,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -65769,7 +65766,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -65865,7 +65862,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -65961,7 +65958,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -66057,7 +66054,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -66153,7 +66150,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -66249,7 +66246,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -66345,7 +66342,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -66441,7 +66438,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -66537,7 +66534,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -66633,7 +66630,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -66729,7 +66726,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -66825,7 +66822,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -66921,7 +66918,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -67017,7 +67014,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -67113,7 +67110,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -67209,7 +67206,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -67306,7 +67303,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -67402,7 +67399,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -67498,7 +67495,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -67594,7 +67591,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -67690,7 +67687,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -67787,7 +67784,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -67883,7 +67880,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -67979,7 +67976,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -68075,7 +68072,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -68171,7 +68168,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -68267,7 +68264,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -68361,7 +68358,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -68456,7 +68453,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -68550,7 +68547,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -68645,7 +68642,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -68743,7 +68740,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -68839,7 +68836,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -68933,7 +68930,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -69028,7 +69025,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -69123,7 +69120,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -69219,7 +69216,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -69314,7 +69311,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -69409,7 +69406,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -69504,7 +69501,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -69599,7 +69596,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -69694,7 +69691,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -69789,7 +69786,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -69884,7 +69881,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -69979,7 +69976,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -70074,7 +70071,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -70169,7 +70166,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -70264,7 +70261,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -70360,7 +70357,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -70455,7 +70452,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -70550,7 +70547,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -70649,7 +70646,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -70752,7 +70749,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -70848,7 +70845,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -70952,7 +70949,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -71048,7 +71045,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -71144,7 +71141,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -71239,7 +71236,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -71338,7 +71335,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -71434,7 +71431,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -71530,7 +71527,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -71629,7 +71626,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -71725,7 +71722,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -71820,7 +71817,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -71903,7 +71900,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -71998,7 +71995,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -72093,7 +72090,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -72189,7 +72186,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -72284,7 +72281,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -72379,7 +72376,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -72475,7 +72472,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -72571,7 +72568,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -72672,7 +72669,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -72773,7 +72770,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -72869,7 +72866,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -72968,7 +72965,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -73063,7 +73060,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -73158,7 +73155,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -73254,7 +73251,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -73350,7 +73347,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -73445,7 +73442,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -73534,7 +73531,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -73627,7 +73624,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -73722,7 +73719,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -73817,7 +73814,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -73912,7 +73909,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -74007,7 +74004,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -74102,7 +74099,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -74196,7 +74193,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -74289,7 +74286,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -74382,7 +74379,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -74475,7 +74472,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -74569,7 +74566,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -74664,7 +74661,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -74759,7 +74756,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -74854,7 +74851,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -74949,7 +74946,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -75044,7 +75041,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -75138,7 +75135,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -75233,7 +75230,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -75328,7 +75325,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -75423,7 +75420,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -75568,7 +75565,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -75664,7 +75661,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -75774,7 +75771,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -75861,7 +75858,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -75955,7 +75952,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -76053,7 +76050,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -76147,7 +76144,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -76242,7 +76239,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -76338,7 +76335,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -76433,7 +76430,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -76530,7 +76527,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -76626,7 +76623,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -76723,7 +76720,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -76831,7 +76828,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -76926,7 +76923,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -77013,7 +77010,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -77109,7 +77106,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -77200,7 +77197,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -77292,7 +77289,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -77384,7 +77381,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -77476,7 +77473,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -77568,7 +77565,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -77660,7 +77657,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -77752,7 +77749,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -77844,7 +77841,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -77936,7 +77933,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -78028,7 +78025,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -78120,7 +78117,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -78212,7 +78209,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -78304,7 +78301,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -78396,7 +78393,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -78488,7 +78485,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -78580,7 +78577,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -78672,7 +78669,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -78764,7 +78761,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -78856,7 +78853,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -78948,7 +78945,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -79038,7 +79035,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -79128,7 +79125,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -79218,7 +79215,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -79308,7 +79305,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -79398,7 +79395,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -79488,7 +79485,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -79578,7 +79575,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -79668,7 +79665,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -79758,7 +79755,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -79848,7 +79845,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -79938,7 +79935,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -80028,7 +80025,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -80118,7 +80115,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -80208,7 +80205,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -80298,7 +80295,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -80390,7 +80387,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -80482,7 +80479,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -80574,7 +80571,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -80666,7 +80663,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -80758,7 +80755,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -80850,7 +80847,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -80942,7 +80939,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -81034,7 +81031,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -81126,7 +81123,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -81218,7 +81215,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -81310,7 +81307,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -81402,7 +81399,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -81494,7 +81491,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -81586,7 +81583,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -81676,7 +81673,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -81766,7 +81763,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -81858,7 +81855,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -82037,7 +82034,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -82134,7 +82131,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -82231,7 +82228,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -82328,7 +82325,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -82425,7 +82422,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -82522,7 +82519,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -82619,7 +82616,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -82718,7 +82715,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -82815,7 +82812,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -82912,7 +82909,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -83010,7 +83007,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -83107,7 +83104,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -83205,7 +83202,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -83302,7 +83299,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -83399,7 +83396,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -83496,7 +83493,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -83593,7 +83590,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -83690,7 +83687,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -83785,7 +83782,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -83881,7 +83878,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -83982,7 +83979,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -84083,7 +84080,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -84179,7 +84176,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -84279,7 +84276,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -84374,7 +84371,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -84470,7 +84467,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -84565,7 +84562,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -84661,7 +84658,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -84756,7 +84753,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -84852,7 +84849,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -85071,7 +85068,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -85166,7 +85163,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -85261,7 +85258,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -85357,7 +85354,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -85452,7 +85449,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -85547,7 +85544,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -85642,7 +85639,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -85737,7 +85734,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -85832,7 +85829,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -85927,7 +85924,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -86022,7 +86019,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -86117,7 +86114,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -86212,7 +86209,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -86307,7 +86304,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -86403,7 +86400,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -86498,7 +86495,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -86593,7 +86590,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -86688,7 +86685,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -86775,7 +86772,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -86869,7 +86866,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -86968,7 +86965,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -87063,7 +87060,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -87158,7 +87155,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -87262,7 +87259,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -87357,7 +87354,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -87444,7 +87441,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -87538,7 +87535,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -87634,7 +87631,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -87728,7 +87725,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -87822,7 +87819,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -87918,7 +87915,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -88013,7 +88010,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -88108,7 +88105,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -88203,7 +88200,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -88224,6 +88221,198 @@
       description_html: "This builder mirrors the following CI builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/linux-fieldtrial-rel\">linux-fieldtrial-rel</a></li></ul>"
     }
     builders {
+      name: "linux-full-remote-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builder:linux-full-remote-rel"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-22.04"
+      dimensions: "pool:luci.chromium.try"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/try/linux-full-remote-rel/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "tryserver.chromium.linux",'
+        '  "cq": "required",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium/orchestrator"'
+        '}'
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      build_numbers: YES
+      service_account: "chromium-orchestrator@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "chromium_swarming.expose_merge_script_failures"
+        value: 100
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
+        key: "swarming.prpc.cli"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*_wpt_tests/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+      description_html: "Experimental <a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-rel\">linux-rel</a> builder with more kinds of remote actions. e.g. remote linking<br/>This builder mirrors the following CI builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/GPU Linux Builder\">GPU Linux Builder</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/Linux Builder\">Linux Builder</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/Linux Release (NVIDIA)\">Linux Release (NVIDIA)</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/Linux Tests\">Linux Tests</a></li></ul><br/>This is the orchestrator half of an orchestrator + compilator pair of builders. The compilator is <a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-full-remote-rel-compilator\">linux-full-remote-rel-compilator</a>."
+      contact_team_email: "chrome-build-team@google.com"
+    }
+    builders {
+      name: "linux-full-remote-rel-compilator"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builder:linux-full-remote-rel-compilator"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-22.04"
+      dimensions: "pool:luci.chromium.try"
+      dimensions: "ssd:1"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/try/linux-full-remote-rel-compilator/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "tryserver.chromium.linux",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium/compilator"'
+        '}'
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "chromium_swarming.expose_merge_script_failures"
+        value: 100
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
+        key: "swarming.prpc.cli"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*_wpt_tests/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+      description_html: "Compilator for linux-full-remote-rel<br/>This is the compilator half of an orchestrator + compilator pair of builders. The orchestrator is <a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-full-remote-rel\">linux-full-remote-rel</a>."
+      contact_team_email: "chrome-build-team@google.com"
+    }
+    builders {
       name: "linux-gcc-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -88298,7 +88487,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -88394,7 +88583,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -88489,7 +88678,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -88585,7 +88774,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -88680,7 +88869,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -88768,7 +88957,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -88862,7 +89051,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -88957,7 +89146,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -89052,7 +89241,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -89155,7 +89344,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -89250,7 +89439,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -89345,7 +89534,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -89441,7 +89630,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -89544,7 +89733,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -89638,7 +89827,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -89733,7 +89922,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -89828,7 +90017,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -89923,7 +90112,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -90019,7 +90208,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -90122,7 +90311,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -90217,7 +90406,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -90312,7 +90501,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -90407,7 +90596,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -90502,7 +90691,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -90594,7 +90783,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -90686,7 +90875,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -90827,7 +91016,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -90919,7 +91108,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -91011,7 +91200,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -91106,7 +91295,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -91201,7 +91390,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -91296,7 +91485,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -91391,7 +91580,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -91486,7 +91675,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -91581,7 +91770,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -91676,7 +91865,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -91771,7 +91960,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -91867,7 +92056,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -91962,7 +92151,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -92058,7 +92247,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -92153,7 +92342,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -92248,7 +92437,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -92343,7 +92532,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -92439,7 +92628,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -92535,7 +92724,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -92630,7 +92819,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -92725,7 +92914,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -92821,7 +93010,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -92928,7 +93117,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -93023,7 +93212,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -93119,7 +93308,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -93214,7 +93403,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -93309,7 +93498,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -93406,7 +93595,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -93508,7 +93697,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -93603,7 +93792,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -93703,7 +93892,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -93798,7 +93987,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -93901,7 +94090,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -93996,7 +94185,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -94091,7 +94280,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -94186,7 +94375,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -94281,7 +94470,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -94502,7 +94691,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -94596,7 +94785,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -94688,7 +94877,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -94783,7 +94972,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -94876,7 +95065,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -94969,7 +95158,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -95063,7 +95252,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -95155,7 +95344,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -95248,7 +95437,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -95342,7 +95531,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -95435,7 +95624,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -95530,7 +95719,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -95624,7 +95813,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -95718,7 +95907,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -95811,7 +96000,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -95905,7 +96094,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -95999,7 +96188,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -96102,7 +96291,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -96195,7 +96384,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -96289,7 +96478,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -96382,7 +96571,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -96472,7 +96661,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -96566,7 +96755,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -96660,7 +96849,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -96754,7 +96943,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -96848,7 +97037,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -96941,7 +97130,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -97035,7 +97224,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -97129,7 +97318,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -97223,7 +97412,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -97317,7 +97506,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -97410,7 +97599,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -97503,7 +97692,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -97597,7 +97786,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -97691,7 +97880,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -97785,7 +97974,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -97879,7 +98068,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -97972,7 +98161,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -98066,7 +98255,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -98160,7 +98349,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -98254,7 +98443,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -98348,7 +98537,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -98441,7 +98630,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -98535,7 +98724,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -98629,7 +98818,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -98722,7 +98911,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -98815,7 +99004,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -98910,7 +99099,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -99005,7 +99194,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -99099,7 +99288,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -99193,7 +99382,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -99292,7 +99481,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -99386,7 +99575,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -99480,7 +99669,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -99574,7 +99763,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -99668,7 +99857,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -99762,7 +99951,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -99856,7 +100045,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -99950,7 +100139,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -100036,7 +100225,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -100132,7 +100321,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -100226,7 +100415,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -100308,7 +100497,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -100395,7 +100584,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -100486,7 +100675,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -100581,7 +100770,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -100676,7 +100865,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -100771,7 +100960,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -100990,7 +101179,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -101133,7 +101322,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -101228,7 +101417,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -101324,7 +101513,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -101420,7 +101609,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -101516,7 +101705,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -101612,7 +101801,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -101708,7 +101897,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -101803,7 +101992,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -101898,7 +102087,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -101993,7 +102182,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -102087,7 +102276,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -102183,7 +102372,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -102267,6 +102456,10 @@
         value: 100
       }
       experiments {
+        key: "luci.buildbucket.backend_alt"
+        value: 0
+      }
+      experiments {
         key: "luci.recipes.use_python3"
         value: 100
       }
@@ -102288,7 +102481,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -102382,7 +102575,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -102477,7 +102670,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -102565,7 +102758,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -102671,7 +102864,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -102766,7 +102959,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -102861,7 +103054,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -102956,7 +103149,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -103051,7 +103244,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -103143,7 +103336,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -103284,7 +103477,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -103425,7 +103618,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -103517,7 +103710,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -103609,7 +103802,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -103705,7 +103898,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -103801,7 +103994,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -103889,7 +104082,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -103983,7 +104176,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -104078,7 +104271,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -104173,7 +104366,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -104269,7 +104462,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -104364,7 +104557,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -104458,7 +104651,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -104552,7 +104745,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -104647,7 +104840,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -104743,7 +104936,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -104838,7 +105031,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -104934,7 +105127,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -105030,7 +105223,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -105125,7 +105318,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -105220,7 +105413,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -105294,6 +105487,10 @@
         value: 100
       }
       experiments {
+        key: "luci.buildbucket.backend_alt"
+        value: 0
+      }
+      experiments {
         key: "luci.recipes.use_python3"
         value: 100
       }
@@ -105315,7 +105512,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -105404,7 +105601,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
@@ -105493,7 +105690,7 @@
           table: "gpu_try_test_results"
           test_results {
             predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
             }
           }
         }
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index 0b91d6c..9d8564e 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -16933,6 +16933,12 @@
     name: "buildbucket/luci.chromium.try/linux-fieldtrial-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/linux-full-remote-rel"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.try/linux-full-remote-rel-compilator"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/linux-gcc-rel"
   }
   builders {
@@ -18336,6 +18342,12 @@
     name: "buildbucket/luci.chromium.try/linux-fieldtrial-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/linux-full-remote-rel"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.try/linux-full-remote-rel-compilator"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/linux-gcc-rel"
   }
   builders {
diff --git a/infra/config/generated/sheriff-rotations/chromium.txt b/infra/config/generated/sheriff-rotations/chromium.txt
index 28a1193..447e98fd 100644
--- a/infra/config/generated/sheriff-rotations/chromium.txt
+++ b/infra/config/generated/sheriff-rotations/chromium.txt
@@ -109,7 +109,6 @@
 ci/linux-presubmit
 ci/linux-ubsan-vptr
 ci/linux-v4l2-codec-rel
-ci/linux-x64-cast-dbg
 ci/mac-archive-rel
 ci/mac-arm64-archive-rel
 ci/mac-arm64-dbg
diff --git a/infra/config/generated/testing/gn_isolate_map.pyl b/infra/config/generated/testing/gn_isolate_map.pyl
index daf6d5f..e7685cd 100644
--- a/infra/config/generated/testing/gn_isolate_map.pyl
+++ b/infra/config/generated/testing/gn_isolate_map.pyl
@@ -1256,6 +1256,10 @@
     "label": "//components/optimization_guide/internal:ondevice_model_example",
     "type": "additional_compile_target",
   },
+  "ondevice_quality_tests": {
+    "label": "//components/optimization_guide/internal/testing:ondevice_quality_tests",
+    "type": "generated_script",
+  },
   "ondevice_stability_tests": {
     "label": "//components/optimization_guide/internal/testing:ondevice_stability_tests",
     "type": "generated_script",
diff --git a/infra/config/generated/testing/mixins.pyl b/infra/config/generated/testing/mixins.pyl
index a43d80e0..6c2074c 100644
--- a/infra/config/generated/testing/mixins.pyl
+++ b/infra/config/generated/testing/mixins.pyl
@@ -376,7 +376,7 @@
     },
   },
   'chromeos-tast-public-builder': {
-    'args': ["tast.setup.FieldTrialConfig=enable", "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.key", "shard_method=hash"],
+    'args': ["tast.setup.FieldTrialConfig=enable", "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.(key|key_type|server)", "shard_method=hash"],
   },
   'chromium-tester-dev-service-account': {
     'swarming': {
diff --git a/infra/config/generated/testing/test_suites.pyl b/infra/config/generated/testing/test_suites.pyl
index b237c74..9a0f7ec 100644
--- a/infra/config/generated/testing/test_suites.pyl
+++ b/infra/config/generated/testing/test_suites.pyl
@@ -4830,6 +4830,33 @@
       },
     },
 
+    'ondevice_quality_tests_suite': {
+      'ondevice_quality_tests': {
+        'mixins': [
+          'has_native_resultdb_integration',
+        ],
+        'linux_args': [
+          '--chromedriver',
+          'chromedriver',
+          '--binary',
+          'chrome',
+        ],
+        'mac_args': [
+          '--chromedriver',
+          'chromedriver',
+          '--binary',
+          'Google Chrome.app/Contents/MacOS/Google Chrome',
+        ],
+        'win_args': [
+          '--chromedriver',
+          'chromedriver.exe',
+          '--binary',
+          'Chrome.exe',
+        ],
+        'experiment_percentage': 100,
+      },
+    },
+
     'ondevice_stability_tests_suite': {
       'ondevice_stability_tests': {
         'mixins': [
@@ -8070,6 +8097,7 @@
           'MODEL_VALIDATION_TRUNK',
         ],
       },
+      'ondevice_quality_tests_suite': {},
       'ondevice_stability_tests_suite': {},
     },
 
@@ -8097,6 +8125,12 @@
           'MODEL_VALIDATION_TRUNK',
         ],
       },
+      'ondevice_quality_tests_suite': {
+        'variants': [
+          'INTEL_UHD_630',
+          'NVIDIA_GEFORCE_GTX_1660',
+        ],
+      },
       'ondevice_stability_tests_suite': {
         'variants': [
           'INTEL_UHD_630',
@@ -8130,6 +8164,13 @@
           'MODEL_VALIDATION_TRUNK',
         ],
       },
+      'ondevice_quality_tests_suite': {
+        'variants': [
+          'AMD_RADEON_RX_5500_XT',
+          'INTEL_UHD_630',
+          'NVIDIA_GEFORCE_GTX_1660',
+        ],
+      },
       'ondevice_stability_tests_suite': {
         'variants': [
           'AMD_RADEON_RX_5500_XT',
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index 8d3e77c7..d457a9a 100644
--- a/infra/config/generated/testing/variants.pyl
+++ b/infra/config/generated/testing/variants.pyl
@@ -251,32 +251,32 @@
   },
   'LACROS_VERSION_SKEW_BETA': {
     'identifier': 'Lacros version skew testing ash beta',
-    'description': 'Run with ash-chrome version 124.0.6367.24',
+    'description': 'Run with ash-chrome version 124.0.6367.34',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v124.0.6367.24',
-          'revision': 'version:124.0.6367.24',
+          'location': 'lacros_version_skew_tests_v124.0.6367.34',
+          'revision': 'version:124.0.6367.34',
         },
       ],
     },
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 125.0.6410.0',
+    'description': 'Run with ash-chrome version 125.0.6411.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v125.0.6410.0',
-          'revision': 'version:125.0.6410.0',
+          'location': 'lacros_version_skew_tests_v125.0.6411.0',
+          'revision': 'version:125.0.6411.0',
         },
       ],
     },
diff --git a/infra/config/lib/ci.star b/infra/config/lib/ci.star
index fd58f4b..49bffc31 100644
--- a/infra/config/lib/ci.star
+++ b/infra/config/lib/ci.star
@@ -114,8 +114,9 @@
             predicate = resultdb.test_result_predicate(
                 # Only match the telemetry_gpu_integration_test target and its
                 # Fuchsia and Android variants that have a suffix added to the
-                # end. Those are caught with [^/]*.
-                test_id_regexp = "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+",
+                # end. Those are caught with [^/]*. The Fuchsia version is in
+                # //content/test since Fuchsia cannot depend on //chrome.
+                test_id_regexp = "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+",
             ),
         ),
         resultdb.export_test_results(
diff --git a/infra/config/lib/try.star b/infra/config/lib/try.star
index 062098c..8a72882 100644
--- a/infra/config/lib/try.star
+++ b/infra/config/lib/try.star
@@ -248,8 +248,9 @@
             predicate = resultdb.test_result_predicate(
                 # Only match the telemetry_gpu_integration_test target and its
                 # Fuchsia and Android variants that have a suffix added to the
-                # end. Those are caught with [^/]*.
-                test_id_regexp = "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+",
+                # end. Those are caught with [^/]*. The Fuchsia version is in
+                # //content/test since Fuchsia cannot depend on //chrome.
+                test_id_regexp = "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+",
             ),
         ),
         resultdb.export_test_results(
diff --git a/infra/config/subprojects/build/build.star b/infra/config/subprojects/build/build.star
index 259693e8..9bcc2f7 100644
--- a/infra/config/subprojects/build/build.star
+++ b/infra/config/subprojects/build/build.star
@@ -96,7 +96,7 @@
 def cq_build_perf_builder(description_html, **kwargs):
     # Use CQ reclient instance and high reclient jobs/cores to simulate CQ builds.
     if not kwargs.get("siso_configs"):
-        kwargs["siso_configs"] = ["builder"]
+        kwargs["siso_configs"] = ["builder", "remote-library-link", "remote-exec-link"]
     return ci.builder(
         description_html = description_html + "<br>Build stats is show in http://shortn/_gaAdI3x6o6.",
         reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CQ,
@@ -516,6 +516,7 @@
         executable = "recipe:chrome_build/build_perf_developer",
         reclient_instance = reclient.instance.DEFAULT_UNTRUSTED,
         siso_project = siso.project.DEFAULT_UNTRUSTED,
+        siso_configs = ["remote-library-link", "remote-exec-link"],
         shadow_reclient_instance = None,
         **kwargs
     )
diff --git a/infra/config/subprojects/chromium/ci/chromium.linux.star b/infra/config/subprojects/chromium/ci/chromium.linux.star
index d7b0849..3e473ac5 100644
--- a/infra/config/subprojects/chromium/ci/chromium.linux.star
+++ b/infra/config/subprojects/chromium/ci/chromium.linux.star
@@ -73,6 +73,8 @@
             "minimal_symbols",
         ],
     ),
+    # TODO(crbug.com/332735845): Garden this once stabilized.
+    sheriff_rotations = args.ignore_default(None),
     tree_closing = False,
     console_view_entry = consoles.console_view_entry(
         category = "cast",
diff --git a/infra/config/subprojects/chromium/ci/chromium.win.star b/infra/config/subprojects/chromium/ci/chromium.win.star
index 0397f65..87d6437 100644
--- a/infra/config/subprojects/chromium/ci/chromium.win.star
+++ b/infra/config/subprojects/chromium/ci/chromium.win.star
@@ -512,8 +512,57 @@
         ],
     ),
     targets = targets.bundle(
-        # TODO(crbug.com/332248571): Add same targets as Win Tests builders.
-        targets = ["base_unittests"],
+        # TODO: crbug.com/332248571 - Add same targets as Win Tests builders.
+        targets = [
+            "absl_hardening_tests",
+            # TODO: crbug.com/333652645 - angle_unittests fail without test results.
+            # https://ci.chromium.org/ui/p/chromium/builders/try/linux-win-cross-rel/13/overview
+            # "angle_unittests",
+            "base_unittests",
+            "blink_common_unittests",
+            "blink_heap_unittests",
+            # TODO: crbug.com/333652645 - Include this target after fixing "Error: local variable mixin referenced before assignment".
+            # "blink_platform_unittests",
+            "boringssl_crypto_tests",
+            "boringssl_ssl_tests",
+            "capture_unittests",
+            "cast_unittests",
+            "components_browsertests",
+            "components_unittests",
+            # TODO: crbug.com/332248571 - Increase swarming shards to avoid timeout.
+            # "content_browsertests",
+            "content_unittests",
+            "crashpad_tests",
+            "crypto_unittests",
+            "env_chromium_unittests",
+            "events_unittests",
+            "gcm_unit_tests",
+            "gin_unittests",
+            "google_apis_unittests",
+            "gpu_unittests",
+            "gwp_asan_unittests",
+            "ipc_tests",
+            "latency_unittests",
+            "leveldb_unittests",
+            "libjingle_xmpp_unittests",
+            "liburlpattern_unittests",
+            "media_unittests",
+            "midi_unittests",
+            "mojo_unittests",
+            "net_unittests",
+            "perfetto_unittests",
+            "services_unittests",
+            "shell_dialogs_unittests",
+            "skia_unittests",
+            "sql_unittests",
+            "storage_unittests",
+            "ui_base_unittests",
+            "ui_touch_selection_unittests",
+            "url_unittests",
+            "webkit_unit_tests",
+            "wtf_unittests",
+            "zlib_unittests",
+        ],
         additional_compile_targets = ["all"],
         mixins = [
             "chromium-tester-service-account",
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 5475cdf9..90816dbd 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -136,7 +136,7 @@
     tree_status_host = "chromium-status.appspot.com" if settings.is_main else None,
     user_limit_default = cq.user_limit(
         name = "default-limit",
-        run = cq.run_limits(max_active = 25),
+        run = cq.run_limits(max_active = 10),
     ),
     user_limits = [
         cq.user_limit(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
index a267343..9c8e6b0f 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
@@ -185,6 +185,13 @@
     mirrors = [
         "ci/fuchsia-x64-cast-receiver-rel",
     ],
+    builder_config_settings = builder_config.try_settings(
+        # This is a temporary solution to avoid allowing culprit changes to slip through since
+        # retry runs without the patch always fail with connection errors.
+        # See https://crbug.com/40278477.
+        # TODO(b/40278477): Re-enable the exoneration when the issue above is fixed.
+        retry_without_patch = False,
+    ),
     gn_args = gn_args.config(
         configs = [
             "ci/fuchsia-x64-cast-receiver-rel",
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
index 943334e..da1c3db 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
@@ -5,10 +5,11 @@
 
 load("//lib/branches.star", "branches")
 load("//lib/builder_config.star", "builder_config")
+load("//lib/builder_url.star", "linkify_builder")
 load("//lib/builders.star", "os", "reclient", "siso")
+load("//lib/consoles.star", "consoles")
 load("//lib/gn_args.star", "gn_args")
 load("//lib/try.star", "try_")
-load("//lib/consoles.star", "consoles")
 load("//project.star", "settings")
 
 try_.defaults.set(
@@ -388,6 +389,27 @@
     main_list_view = "try",
 )
 
+try_.orchestrator_builder(
+    name = "linux-full-remote-rel",
+    description_html = "Experimental " + linkify_builder("try", "linux-rel", "chromium") + " builder with more kinds of remote actions. e.g. remote linking",
+    mirrors = builder_config.copy_from("linux-rel"),
+    gn_args = "try/linux-rel",
+    compilator = "linux-full-remote-rel-compilator",
+    contact_team_email = "chrome-build-team@google.com",
+    siso_configs = ["builder", "remote-library-link", "remote-exec-link"],
+    tryjob = try_.job(
+        experiment_percentage = 10,
+    ),
+    use_clang_coverage = True,
+)
+
+try_.compilator_builder(
+    name = "linux-full-remote-rel-compilator",
+    # TODO: compilator_builder doesn't need description_html as it's automatically generated.
+    description_html = "Compilator for linux-full-remote-rel",
+    contact_team_email = "chrome-build-team@google.com",
+)
+
 # TODO(crbug.com/1394755): Remove this builder after burning down failures
 # and measuring performance to see if we can roll UBSan into ASan.
 try_.builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.win.star b/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
index 10214f8..f7f6eeb 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
@@ -113,6 +113,8 @@
     experiments = {
         # crbug/940930
         "chromium.enable_cleandead": 100,
+        # TODO(b/333068134): remove after the bug is fixed.
+        "luci.buildbucket.backend_alt": 0,
     },
     main_list_view = "try",
     reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CQ,
@@ -498,8 +500,9 @@
     os = os.WINDOWS_DEFAULT,
     # default is 6 in _gpu_optional_tests_builder()
     execution_timeout = 5 * time.hour,
+    # TODO(b/333068134): remove after the bug is fixed.
+    experiments = {"luci.buildbucket.backend_alt": 0},
     main_list_view = "try",
-    siso_enabled = False,
     tryjob = try_.job(
         location_filters = [
             # Inclusion filters.
diff --git a/infra/config/targets/basic_suites.star b/infra/config/targets/basic_suites.star
index e0909c0..e5d6b0b 100644
--- a/infra/config/targets/basic_suites.star
+++ b/infra/config/targets/basic_suites.star
@@ -4534,6 +4534,37 @@
 )
 
 targets.legacy_basic_suite(
+    name = "ondevice_quality_tests_suite",
+    tests = {
+        "ondevice_quality_tests": targets.legacy_test_config(
+            mixins = [
+                "has_native_resultdb_integration",
+            ],
+            linux_args = [
+                "--chromedriver",
+                "chromedriver",
+                "--binary",
+                "chrome",
+            ],
+            mac_args = [
+                "--chromedriver",
+                "chromedriver",
+                "--binary",
+                "Google Chrome.app/Contents/MacOS/Google Chrome",
+            ],
+            win_args = [
+                "--chromedriver",
+                "chromedriver.exe",
+                "--binary",
+                "Chrome.exe",
+            ],
+            # Set suite to experimental until stable.
+            experiment_percentage = 100,
+        ),
+    },
+)
+
+targets.legacy_basic_suite(
     name = "ondevice_stability_tests_suite",
     tests = {
         "ondevice_stability_tests": targets.legacy_test_config(
diff --git a/infra/config/targets/binaries.star b/infra/config/targets/binaries.star
index 8a2bad7..41a6fdb1 100644
--- a/infra/config/targets/binaries.star
+++ b/infra/config/targets/binaries.star
@@ -1336,6 +1336,11 @@
 )
 
 targets.binaries.generated_script(
+    name = "ondevice_quality_tests",
+    label = "//components/optimization_guide/internal/testing:ondevice_quality_tests",
+)
+
+targets.binaries.generated_script(
     name = "ondevice_stability_tests",
     label = "//components/optimization_guide/internal/testing:ondevice_stability_tests",
 )
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index e54d3e1a..a5519a0 100644
--- a/infra/config/targets/lacros-version-skew-variants.json
+++ b/infra/config/targets/lacros-version-skew-variants.json
@@ -1,16 +1,16 @@
 {
   "LACROS_VERSION_SKEW_CANARY": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 125.0.6410.0",
+    "description": "Run with ash-chrome version 125.0.6411.0",
     "identifier": "Lacros version skew testing ash canary",
     "swarming": {
       "cipd_packages": [
         {
           "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-          "location": "lacros_version_skew_tests_v125.0.6410.0",
-          "revision": "version:125.0.6410.0"
+          "location": "lacros_version_skew_tests_v125.0.6411.0",
+          "revision": "version:125.0.6411.0"
         }
       ]
     }
@@ -33,16 +33,16 @@
   },
   "LACROS_VERSION_SKEW_BETA": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 124.0.6367.24",
+    "description": "Run with ash-chrome version 124.0.6367.34",
     "identifier": "Lacros version skew testing ash beta",
     "swarming": {
       "cipd_packages": [
         {
           "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-          "location": "lacros_version_skew_tests_v124.0.6367.24",
-          "revision": "version:124.0.6367.24"
+          "location": "lacros_version_skew_tests_v124.0.6367.34",
+          "revision": "version:124.0.6367.34"
         }
       ]
     }
diff --git a/infra/config/targets/matrix_compound_suites.star b/infra/config/targets/matrix_compound_suites.star
index 2b0adec..d593c5d 100644
--- a/infra/config/targets/matrix_compound_suites.star
+++ b/infra/config/targets/matrix_compound_suites.star
@@ -1483,6 +1483,7 @@
                 "MODEL_VALIDATION_TRUNK",
             ],
         ),
+        "ondevice_quality_tests_suite": None,
         "ondevice_stability_tests_suite": None,
     },
 )
@@ -1517,6 +1518,12 @@
                 "MODEL_VALIDATION_TRUNK",
             ],
         ),
+        "ondevice_quality_tests_suite": targets.legacy_matrix_config(
+            variants = [
+                "INTEL_UHD_630",
+                "NVIDIA_GEFORCE_GTX_1660",
+            ],
+        ),
         "ondevice_stability_tests_suite": targets.legacy_matrix_config(
             variants = [
                 "INTEL_UHD_630",
@@ -1556,6 +1563,13 @@
                 "MODEL_VALIDATION_TRUNK",
             ],
         ),
+        "ondevice_quality_tests_suite": targets.legacy_matrix_config(
+            variants = [
+                "AMD_RADEON_RX_5500_XT",
+                "INTEL_UHD_630",
+                "NVIDIA_GEFORCE_GTX_1660",
+            ],
+        ),
         "ondevice_stability_tests_suite": targets.legacy_matrix_config(
             variants = [
                 "AMD_RADEON_RX_5500_XT",
diff --git a/infra/config/targets/mixins.star b/infra/config/targets/mixins.star
index 452f34c..2ca46771 100644
--- a/infra/config/targets/mixins.star
+++ b/infra/config/targets/mixins.star
@@ -431,7 +431,7 @@
 
             # Tests using the default gaia pool cannot be run by public builders.
             # These variables are fed by private bundles, thus not for public builders.
-            "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.key",
+            "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.(key|key_type|server)",
 
             # Use "hash" method to shrding of test tests. This should balance the
             # execution time among shards in a better way.
diff --git a/infra/config/targets/tests.star b/infra/config/targets/tests.star
index 8bfb2e6..9ebe628 100644
--- a/infra/config/targets/tests.star
+++ b/infra/config/targets/tests.star
@@ -1585,6 +1585,10 @@
 )
 
 targets.tests.isolated_script_test(
+    name = "ondevice_quality_tests",
+)
+
+targets.tests.isolated_script_test(
     name = "ondevice_stability_tests",
 )
 
diff --git a/internal b/internal
index 18e908e..86281eb 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 18e908ec2a27af947b47b16f90a2872121f332c7
+Subproject commit 86281eb09ff42ce162e85fc16de5f421c9fb0326
diff --git a/ios/build/bots/scripts/test_runner.py b/ios/build/bots/scripts/test_runner.py
index d02d669..df621c0e 100644
--- a/ios/build/bots/scripts/test_runner.py
+++ b/ios/build/bots/scripts/test_runner.py
@@ -1172,4 +1172,8 @@
     if xcode_util.using_xcode_15_or_higher():
       LOGGER.warning(
           "Restarting usbmuxd to ensure device is re-paired to Xcode...")
-      mac_util.stop_usbmuxd()
+      try:
+        mac_util.stop_usbmuxd()
+      except subprocess.CalledProcessError as e:
+        logging.exception('Unable to restart usbmuxd:')
+        logging.error(e)
diff --git a/ios/chrome/browser/content_notification/model/content_notification_client.mm b/ios/chrome/browser/content_notification/model/content_notification_client.mm
index c0d161b..d4afd03 100644
--- a/ios/chrome/browser/content_notification/model/content_notification_client.mm
+++ b/ios/chrome/browser/content_notification/model/content_notification_client.mm
@@ -4,9 +4,10 @@
 
 #import "ios/chrome/browser/content_notification/model/content_notification_client.h"
 
-#import "ios/chrome/browser/push_notification/model/constants.h"
+#import "base/metrics/histogram_functions.h"
 #import "ios/chrome/browser/content_notification/model/content_notification_service.h"
 #import "ios/chrome/browser/content_notification/model/content_notification_service_factory.h"
+#import "ios/chrome/browser/push_notification/model/constants.h"
 #import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
 #import "ios/chrome/grit/ios_branded_strings.h"
 #import "ios/chrome/grit/ios_strings.h"
@@ -22,15 +23,24 @@
     UNNotificationResponse* response) {
   NSDictionary<NSString*, id>* payload =
       response.notification.request.content.userInfo;
+  ContentNotificationService* contentNotificationService =
+      ContentNotificationServiceFactory::GetForBrowserState(
+          GetLastUsedBrowserState());
   if ([response.actionIdentifier
           isEqualToString:kContentNotificationFeedbackActionIdentifier]) {
-    loadFeedback();
+    NSDictionary<NSString*, NSString*>* feedbackPayload =
+        contentNotificationService->GetFeedbackPayload(payload);
+    loadFeedbackWithPayloadAndClientId(feedbackPayload,
+                                       PushNotificationClientId::kContent);
   } else {
-    ContentNotificationService* contentNotificationService =
-        ContentNotificationServiceFactory::GetForBrowserState(
-            GetLastUsedBrowserState());
-
     const GURL& url = contentNotificationService->GetDestinationUrl(payload);
+    if (url.is_empty()) {
+      base::UmaHistogramBoolean("ContentNotifications.OpenURLAction.HasURL",
+                                false);
+      loadUrlInNewTab(GURL("chrome://newtab"));
+    }
+    base::UmaHistogramBoolean("ContentNotifications.OpenURLAction.HasURL",
+                              true);
     loadUrlInNewTab(url);
   }
 }
diff --git a/ios/chrome/browser/content_notification/model/content_notification_service.h b/ios/chrome/browser/content_notification/model/content_notification_service.h
index c140c31..2e2bbee 100644
--- a/ios/chrome/browser/content_notification/model/content_notification_service.h
+++ b/ios/chrome/browser/content_notification/model/content_notification_service.h
@@ -19,6 +19,11 @@
   // Returns a destination URL from an unparsed content notification payload.
   virtual GURL GetDestinationUrl(NSDictionary<NSString*, id>* payload) = 0;
 
+  // Returns a payload to be sent for feedback from a content notification
+  // payload.
+  virtual NSDictionary<NSString*, NSString*>* GetFeedbackPayload(
+      NSDictionary<NSString*, id>* payload) = 0;
+
   // KeyedService implementation.
   void Shutdown() override;
 };
diff --git a/ios/chrome/browser/passwords/model/BUILD.gn b/ios/chrome/browser/passwords/model/BUILD.gn
index d3dffca8..421489a 100644
--- a/ios/chrome/browser/passwords/model/BUILD.gn
+++ b/ios/chrome/browser/passwords/model/BUILD.gn
@@ -307,6 +307,7 @@
     ":eg_test_support+eg2",
     "//base",
     "//base/test:test_support",
+    "//components/autofill/ios/common",
     "//components/password_manager/core/common",
     "//components/strings",
     "//components/sync/base",
diff --git a/ios/chrome/browser/passwords/model/password_controller_egtest.mm b/ios/chrome/browser/passwords/model/password_controller_egtest.mm
index 683a807..3c7bdf60 100644
--- a/ios/chrome/browser/passwords/model/password_controller_egtest.mm
+++ b/ios/chrome/browser/passwords/model/password_controller_egtest.mm
@@ -9,6 +9,8 @@
 
 #import "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
+#import "base/time/time.h"
+#import "components/autofill/ios/common/features.h"
 #import "components/password_manager/core/common/password_manager_features.h"
 #import "components/strings/grit/components_strings.h"
 #import "components/sync/base/user_selectable_type.h"
@@ -105,6 +107,8 @@
   if ([self isRunningTest:@selector(testUpdatePromptAppearsOnFormSubmission)]) {
     config.features_enabled.push_back(
         password_manager::features::kIOSPasswordBottomSheet);
+  } else if ([self isRunningTest:@selector(testStickySavePromptJourney)]) {
+    config.features_enabled.push_back(kAutofillStickyInfobarIos);
   }
   return config;
 }
@@ -243,6 +247,71 @@
   GREYAssertEqual(1, credentialsCount, @"Wrong number of final credentials.");
 }
 
+// Tests the sticky password prompt journey where the prompt remains there when
+// navigating without an explicit user gesture, and then the prompt is dismissed
+// when navigating with a user gesture. Test with the password save prompt but
+// the type of password prompt doesn't matter in this test case.
+- (void)testStickySavePromptJourney {
+  [ChromeEarlGrey loadURL:self.testServer->GetURL("/simple_login_form.html")];
+  [ChromeEarlGrey waitForWebStateContainingText:"Login form."];
+
+  // Emulate user interacting with fields.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+      performAction:chrome_test_util::TapWebElementWithId(kFormUsername)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+      performAction:chrome_test_util::TapWebElementWithId(kFormPassword)];
+
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+      performAction:chrome_test_util::TapWebElementWithId("submit_button")];
+
+  // Wait until the save password prompt becomes visible.
+  [ChromeEarlGrey
+      waitForUIElementToAppearWithMatcher:
+          PasswordInfobarLabels(IDS_IOS_PASSWORD_MANAGER_SAVE_PASSWORD_PROMPT)];
+
+  {
+    // Reloading page from script shouldn't dismiss the infobar.
+    NSString* script = @"location.reload();";
+    [ChromeEarlGrey evaluateJavaScriptForSideEffect:script];
+  }
+  {
+    // Assigning url from script to the page aka open an url shouldn't dismiss
+    // the infobar.
+    NSString* script = @"window.location.assign(window.location.href);";
+    [ChromeEarlGrey evaluateJavaScriptForSideEffect:script];
+  }
+  {
+    // Pushing new history entry without reloading content shouldn't dismiss the
+    // infobar.
+    NSString* script = @"history.pushState({}, '', 'destination2.html');";
+    [ChromeEarlGrey evaluateJavaScriptForSideEffect:script];
+  }
+  {
+    // Replacing history entry without reloading content shouldn't dismiss the
+    // infobar.
+    NSString* script = @"history.replaceState({}, '', 'destination3.html');";
+    [ChromeEarlGrey evaluateJavaScriptForSideEffect:script];
+  }
+
+  // Wait some time for things to settle.
+  base::test::ios::SpinRunLoopWithMinDelay(base::Milliseconds(200));
+
+  // Verify that the prompt is still there after the non-user initiated
+  // navigations.
+  [[EarlGrey
+      selectElementWithMatcher:
+          PasswordInfobarLabels(IDS_IOS_PASSWORD_MANAGER_SAVE_PASSWORD_PROMPT)]
+      assertWithMatcher:grey_sufficientlyVisible()];
+
+  // Navigate with an emulated user gesture.
+  [ChromeEarlGrey loadURL:self.testServer->GetURL("/simple_login_form.html")];
+
+  // Verify that the infobar is dismissed.
+  [ChromeEarlGrey
+      waitForUIElementToDisappearWithMatcher:
+          PasswordInfobarLabels(IDS_IOS_PASSWORD_MANAGER_UPDATE_PASSWORD)];
+}
+
 // Tests password generation flow.
 // TODO(crbug.com/1423865): The test fails on simulator.
 #if TARGET_IPHONE_SIMULATOR
diff --git a/ios/chrome/browser/providers/content_notification/chromium_content_notification_service.mm b/ios/chrome/browser/providers/content_notification/chromium_content_notification_service.mm
index 795eb23b..fd8fd04 100644
--- a/ios/chrome/browser/providers/content_notification/chromium_content_notification_service.mm
+++ b/ios/chrome/browser/providers/content_notification/chromium_content_notification_service.mm
@@ -17,6 +17,10 @@
   GURL GetDestinationUrl(NSDictionary<NSString*, id>* payload) final {
     return GURL::EmptyGURL();
   }
+  NSDictionary<NSString*, NSString*>* GetFeedbackPayload(
+      NSDictionary<NSString*, id>* payload) final {
+    return nil;
+  }
 };
 
 }  // anonymous namespace
diff --git a/ios/chrome/browser/push_notification/model/push_notification_client.h b/ios/chrome/browser/push_notification/model/push_notification_client.h
index 409a3bb..82c1887 100644
--- a/ios/chrome/browser/push_notification/model/push_notification_client.h
+++ b/ios/chrome/browser/push_notification/model/push_notification_client.h
@@ -65,7 +65,9 @@
   void loadUrlInNewTab(const GURL& url);
 
   // Loads the feedback view controller once an active browser is ready.
-  void loadFeedback();
+  void loadFeedbackWithPayloadAndClientId(
+      NSDictionary<NSString*, NSString*>* data,
+      PushNotificationClientId clientId);
 
   // Allows tests to set the last used ChromeBrowserState returned in
   // GetLastUsedBrowserState().
@@ -93,6 +95,12 @@
   // Stores whether or not the feedback view controller should be shown when a
   // Browser is ready.
   bool feedback_presentation_delayed_ = false;
+
+  // Stores which client sent the delayed feedback request.
+  PushNotificationClientId feedback_presentation_delayed_client_;
+
+  // Stores the feedback payload to be sent with the notification feedback.
+  NSDictionary<NSString*, NSString*>* feedback_data_ = nil;
   // Allows tests to override the last used ChromeBrowserState returned in
   // GetLastUsedBrowserState().
   raw_ptr<ChromeBrowserState> last_used_browser_state_for_testing_ = nullptr;
diff --git a/ios/chrome/browser/push_notification/model/push_notification_client.mm b/ios/chrome/browser/push_notification/model/push_notification_client.mm
index e5b4b52..a5701d0 100644
--- a/ios/chrome/browser/push_notification/model/push_notification_client.mm
+++ b/ios/chrome/browser/push_notification/model/push_notification_client.mm
@@ -34,13 +34,25 @@
   if (feedback_presentation_delayed_) {
     id<ApplicationCommands> handler =
         static_cast<id<ApplicationCommands>>(browser->GetCommandDispatcher());
-    // TODO(b/328827101): Add payload from notification to send alongside the
-    // feedback.
-    [handler showReportAnIssueFromViewController:browser->GetSceneState()
-                                                     .window.rootViewController
-                                          sender:UserFeedbackSender::
-                                                     ContentNotification];
-    feedback_presentation_delayed_ = false;
+    switch (feedback_presentation_delayed_client_) {
+      case PushNotificationClientId::kContent:
+      case PushNotificationClientId::kSports:
+        [handler
+            showReportAnIssueFromViewController:browser->GetSceneState()
+                                                    .window.rootViewController
+                                         sender:UserFeedbackSender::
+                                                    ContentNotification
+                            specificProductData:feedback_data_];
+        feedback_presentation_delayed_ = false;
+        break;
+      case PushNotificationClientId::kTips:
+      case PushNotificationClientId::kCommerce:
+        // Features do not support feedback.
+        NOTREACHED();
+        break;
+      default:
+        break;
+    }
   }
   if (urls_delayed_for_loading_.size()) {
     for (const GURL& url : urls_delayed_for_loading_) {
@@ -83,10 +95,14 @@
   UrlLoadingBrowserAgent::FromBrowser(browser)->Load(params);
 }
 
-void PushNotificationClient::loadFeedback() {
+void PushNotificationClient::loadFeedbackWithPayloadAndClientId(
+    NSDictionary<NSString*, NSString*>* data,
+    PushNotificationClientId client) {
   Browser* browser = GetSceneLevelForegroundActiveBrowser();
-  if (!browser) {
+  if (!browser && data) {
+    feedback_presentation_delayed_client_ = client;
     feedback_presentation_delayed_ = true;
+    feedback_data_ = data;
     return;
   }
 }
diff --git a/ios/chrome/browser/ui/autofill/authentication/BUILD.gn b/ios/chrome/browser/ui/autofill/authentication/BUILD.gn
index be0a627..193d3483e 100644
--- a/ios/chrome/browser/ui/autofill/authentication/BUILD.gn
+++ b/ios/chrome/browser/ui/autofill/authentication/BUILD.gn
@@ -27,6 +27,10 @@
     "otp_input_dialog_consumer.h",
     "otp_input_dialog_content.h",
     "otp_input_dialog_content.mm",
+    "otp_input_dialog_mutator.h",
+    "otp_input_dialog_mutator_bridge.h",
+    "otp_input_dialog_mutator_bridge.mm",
+    "otp_input_dialog_mutator_bridge_target.h",
     "otp_input_dialog_view_controller.h",
     "otp_input_dialog_view_controller.mm",
   ]
@@ -125,6 +129,7 @@
     ":otp_input_dialog_ui",
     "//base:base",
     "//components/autofill/core/browser:browser",
+    "//testing/gmock:gmock",
     "//testing/gtest:gtest",
     "//third_party/ocmock",
   ]
diff --git a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_coordinator.mm b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_coordinator.mm
index 7e98f7af..374d8d1 100644
--- a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_coordinator.mm
+++ b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_coordinator.mm
@@ -63,6 +63,7 @@
   auto viewController = [[OtpInputDialogViewController alloc] init];
   _viewController = viewController;
   _mediator->SetConsumer(_viewController);
+  _viewController.mutator = _mediator->AsMutator();
   [_baseNavigationController pushViewController:viewController animated:YES];
 }
 
diff --git a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator.h b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator.h
index eb0ab88..83f64b8a 100644
--- a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator.h
+++ b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator.h
@@ -5,13 +5,16 @@
 #ifndef IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_OTP_INPUT_DIALOG_MEDIATOR_H_
 #define IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_OTP_INPUT_DIALOG_MEDIATOR_H_
 
-#import "components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_view.h"
-
 #import <Foundation/Foundation.h>
 
 #import "base/memory/weak_ptr.h"
+#import "components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_view.h"
+#import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge_target.h"
 
 @protocol OtpInputDialogConsumer;
+@protocol OtpInputDialogMutator;
+
+@class OtpInputDialogMutatorBridge;
 
 namespace autofill {
 class CardUnmaskOtpInputDialogControllerImpl;
@@ -19,7 +22,8 @@
 
 // Bridge class used to connect Autofill OTP input dialog components with the
 // IOS view implementation.
-class OtpInputDialogMediator : public autofill::CardUnmaskOtpInputDialogView {
+class OtpInputDialogMediator : public autofill::CardUnmaskOtpInputDialogView,
+                               public OtpInputDialogMutatorBridgeTarget {
  public:
   explicit OtpInputDialogMediator(
       base::WeakPtr<autofill::CardUnmaskOtpInputDialogControllerImpl>
@@ -35,8 +39,18 @@
                bool user_closed_dialog) override;
   base::WeakPtr<CardUnmaskOtpInputDialogView> GetWeakPtr() override;
 
+  // OtpInputDialogMutatorTarget:
+  void DidTapConfirmButton(const std::u16string& input_value) override;
+  void DidTapCancelButton() override;
+  void OnOtpInputChanges(const std::u16string& input_value) override;
+
   void SetConsumer(id<OtpInputDialogConsumer> consumer);
 
+  // Returns an implementation of the mutator that forwards to this mediator.
+  // We need this bridge since this mediator is C++ whereas the ViewController
+  // expects the Objective-C protocol.
+  id<OtpInputDialogMutator> AsMutator();
+
  private:
   // The model to provide data to be shown in the IOS view implementation.
   base::WeakPtr<autofill::CardUnmaskOtpInputDialogControllerImpl>
@@ -44,6 +58,8 @@
 
   __weak id<OtpInputDialogConsumer> consumer_;
 
+  OtpInputDialogMutatorBridge* mutator_bridge_;
+
   base::WeakPtrFactory<OtpInputDialogMediator> weak_ptr_factory_{this};
 };
 
diff --git a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator.mm b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator.mm
index 7a145a7..59fdc69 100644
--- a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator.mm
+++ b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator.mm
@@ -11,11 +11,19 @@
 #import "components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.h"
 #import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_consumer.h"
 #import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_content.h"
+#import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator.h"
+#import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge.h"
+#import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge_target.h"
 
 OtpInputDialogMediator::OtpInputDialogMediator(
     base::WeakPtr<autofill::CardUnmaskOtpInputDialogControllerImpl>
         model_controller)
-    : model_controller_(model_controller) {}
+    : model_controller_(model_controller) {
+  base::WeakPtr<OtpInputDialogMutatorBridgeTarget>
+      mutator_bridge_target_weak_ptr(weak_ptr_factory_.GetWeakPtr());
+  mutator_bridge_ = [[OtpInputDialogMutatorBridge alloc]
+      initWithTarget:mutator_bridge_target_weak_ptr];
+}
 
 OtpInputDialogMediator::~OtpInputDialogMediator() {
   // If the closure is not initiated from the backend side (via Dismiss()), it
@@ -52,6 +60,27 @@
   return weak_ptr_factory_.GetWeakPtr();
 }
 
+void OtpInputDialogMediator::DidTapConfirmButton(
+    const std::u16string& input_value) {
+  if (model_controller_) {
+    model_controller_->OnOkButtonClicked(input_value);
+    [consumer_ showPendingState];
+  }
+}
+
+void OtpInputDialogMediator::DidTapCancelButton() {
+  // TODO(crbug.com/324611313): Handle this via the view presentation delegate
+  // to notify the coordinator.
+}
+
+void OtpInputDialogMediator::OnOtpInputChanges(
+    const std::u16string& input_value) {
+  if (model_controller_) {
+    [consumer_
+        setConfirmButtonEnabled:model_controller_->IsValidOtp(input_value)];
+  }
+}
+
 void OtpInputDialogMediator::SetConsumer(id<OtpInputDialogConsumer> consumer) {
   consumer_ = consumer;
   if (!model_controller_) {
@@ -66,3 +95,7 @@
       base::SysUTF16ToNSString(model_controller_->GetOkButtonLabel());
   [consumer_ setContent:content];
 }
+
+id<OtpInputDialogMutator> OtpInputDialogMediator::AsMutator() {
+  return mutator_bridge_;
+}
diff --git a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator_unittest.mm b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator_unittest.mm
index 8ec0f962..4d5f1c4 100644
--- a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator_unittest.mm
@@ -4,16 +4,44 @@
 
 #import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mediator.h"
 
+#import "base/memory/weak_ptr.h"
 #import "base/strings/sys_string_conversions.h"
+#import "components/autofill/core/browser/payments/otp_unmask_delegate.h"
 #import "components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.h"
 #import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_consumer.h"
 #import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_content.h"
+#import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator.h"
+#import "testing/gmock/include/gmock/gmock.h"
 #import "testing/platform_test.h"
 #import "third_party/ocmock/OCMock/OCMock.h"
 #import "third_party/ocmock/gtest_support.h"
 
 using autofill::CardUnmaskChallengeOption;
 
+// Mock version of CardUnmaskOtpInputDialogController.
+class MockOtpUnmaskDelegate : public autofill::OtpUnmaskDelegate {
+ public:
+  MockOtpUnmaskDelegate() = default;
+  ~MockOtpUnmaskDelegate() = default;
+
+  MOCK_METHOD(void,
+              OnUnmaskPromptAccepted,
+              (const std::u16string& otp),
+              (override));
+  MOCK_METHOD(void,
+              OnUnmaskPromptClosed,
+              (bool user_closed_dialog),
+              (override));
+  MOCK_METHOD(void, OnNewOtpRequested, (), (override));
+
+  base::WeakPtr<MockOtpUnmaskDelegate> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+ private:
+  base::WeakPtrFactory<MockOtpUnmaskDelegate> weak_ptr_factory_{this};
+};
+
 class OtpInputDialogMediatorTest : public PlatformTest {
  protected:
   OtpInputDialogMediatorTest() {
@@ -25,12 +53,13 @@
         /*challenge_input_length=*/6U);
     model_controller_ =
         std::make_unique<autofill::CardUnmaskOtpInputDialogControllerImpl>(
-            option, /*delegate=*/nullptr);
+            option, unmask_delegate_.GetWeakPtr());
     mediator_ = std::make_unique<OtpInputDialogMediator>(
         model_controller_->GetImplWeakPtr());
   }
 
   id<OtpInputDialogConsumer> consumer_;
+  testing::NiceMock<MockOtpUnmaskDelegate> unmask_delegate_;
   std::unique_ptr<autofill::CardUnmaskOtpInputDialogControllerImpl>
       model_controller_;
   std::unique_ptr<OtpInputDialogMediator> mediator_;
@@ -57,3 +86,27 @@
 
   EXPECT_OCMOCK_VERIFY((id)consumer_);
 }
+
+TEST_F(OtpInputDialogMediatorTest, DidTapConfirmButton) {
+  NSString* otp = @"123456";
+  OCMExpect([consumer_ showPendingState]);
+  EXPECT_CALL(unmask_delegate_,
+              OnUnmaskPromptAccepted(base::SysNSStringToUTF16(otp)));
+
+  [mediator_->AsMutator() didTapConfirmButton:otp];
+}
+
+TEST_F(OtpInputDialogMediatorTest, DidTapCancelButton) {
+  // TODO(crbug.com/324611313): Finish this test when the mediator delegate is
+  // added.
+}
+
+TEST_F(OtpInputDialogMediatorTest, OnOtpInputChanges) {
+  OCMExpect([consumer_ setConfirmButtonEnabled:NO]);
+
+  [mediator_->AsMutator() onOtpInputChanges:@"12345"];
+
+  OCMExpect([consumer_ setConfirmButtonEnabled:YES]);
+
+  [mediator_->AsMutator() onOtpInputChanges:@"123456"];
+}
diff --git a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator.h b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator.h
new file mode 100644
index 0000000..9f77b5f
--- /dev/null
+++ b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator.h
@@ -0,0 +1,24 @@
+// 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_AUTOFILL_AUTHENTICATION_OTP_INPUT_DIALOG_MUTATOR_H_
+#define IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_OTP_INPUT_DIALOG_MUTATOR_H_
+
+#import <Foundation/Foundation.h>
+
+@protocol OtpInputDialogMutator <NSObject>
+
+// Invoked when the confirm button in the navigation bar is tapped by the user.
+// This means a valid OTP value is typed in.
+- (void)didTapConfirmButton:(NSString*)inputValue;
+
+// Invoked when the cancel button in the navigation bar is tapped by the user.
+- (void)didTapCancelButton;
+
+// Notify the model controller when the OTP input value changes.
+- (void)onOtpInputChanges:(NSString*)inputValue;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_OTP_INPUT_DIALOG_MUTATOR_H_
diff --git a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge.h b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge.h
new file mode 100644
index 0000000..9c4a7d6c
--- /dev/null
+++ b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge.h
@@ -0,0 +1,25 @@
+// 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_AUTOFILL_AUTHENTICATION_OTP_INPUT_DIALOG_MUTATOR_BRIDGE_H_
+#define IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_OTP_INPUT_DIALOG_MUTATOR_BRIDGE_H_
+
+#import <Foundation/Foundation.h>
+
+#import "base/memory/weak_ptr.h"
+#import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator.h"
+
+class OtpInputDialogMutatorBridgeTarget;
+
+// This class implements the Objective-C protocol and forwards the messages to
+// the C++ abstract class.
+@interface OtpInputDialogMutatorBridge : NSObject <OtpInputDialogMutator>
+
+// Create the bridge given the C++ target.
+- (instancetype)initWithTarget:
+    (base::WeakPtr<OtpInputDialogMutatorBridgeTarget>)target;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_OTP_INPUT_DIALOG_MUTATOR_BRIDGE_H_
diff --git a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge.mm b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge.mm
new file mode 100644
index 0000000..4f28cc7
--- /dev/null
+++ b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge.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/autofill/authentication/otp_input_dialog_mutator_bridge.h"
+
+#import "base/strings/sys_string_conversions.h"
+#import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge_target.h"
+
+@implementation OtpInputDialogMutatorBridge {
+  base::WeakPtr<OtpInputDialogMutatorBridgeTarget> _target;
+}
+
+- (instancetype)initWithTarget:
+    (base::WeakPtr<OtpInputDialogMutatorBridgeTarget>)target {
+  self = [super init];
+  if (self) {
+    _target = target;
+  }
+  return self;
+}
+
+#pragma mark - OtpInputDialogMutator
+
+- (void)didTapConfirmButton:(NSString*)inputValue {
+  if (_target) {
+    _target->DidTapConfirmButton(base::SysNSStringToUTF16(inputValue));
+  }
+}
+
+- (void)didTapCancelButton {
+  if (_target) {
+    _target->DidTapCancelButton();
+  }
+}
+
+- (void)onOtpInputChanges:(NSString*)inputValue {
+  if (_target) {
+    _target->OnOtpInputChanges(base::SysNSStringToUTF16(inputValue));
+  }
+}
+
+@end
diff --git a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge_target.h b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge_target.h
new file mode 100644
index 0000000..65f1537
--- /dev/null
+++ b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator_bridge_target.h
@@ -0,0 +1,19 @@
+// 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_AUTOFILL_AUTHENTICATION_OTP_INPUT_DIALOG_MUTATOR_BRIDGE_TARGET_H_
+#define IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_OTP_INPUT_DIALOG_MUTATOR_BRIDGE_TARGET_H_
+
+#import <string>
+
+// The C++ equivalent interface of the Objective-C protocol,
+// OtpInputDialogMutator.
+class OtpInputDialogMutatorBridgeTarget {
+ public:
+  virtual void DidTapConfirmButton(const std::u16string& input_value) = 0;
+  virtual void DidTapCancelButton() = 0;
+  virtual void OnOtpInputChanges(const std::u16string& input_value) = 0;
+};
+
+#endif  // IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_OTP_INPUT_DIALOG_MUTATOR_BRIDGE_TARGET_H_
diff --git a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.h b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.h
index 5da4f14..01f2214 100644
--- a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.h
+++ b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.h
@@ -8,11 +8,16 @@
 #import "ios/chrome/browser/shared/ui/table_view/chrome_table_view_controller.h"
 #import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_consumer.h"
 
+@protocol OtpInputDialogMutator;
+
 // Controller for the UI that allows the user to enter an OTP for card
 // verification purposes.
 @interface OtpInputDialogViewController
     : ChromeTableViewController <OtpInputDialogConsumer>
 
+// The delegate for user actions.
+@property(nonatomic, weak) id<OtpInputDialogMutator> mutator;
+
 - (instancetype)init;
 
 @end
diff --git a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.mm b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.mm
index a656144..0ab0abc 100644
--- a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.mm
+++ b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.mm
@@ -10,6 +10,7 @@
 #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_edit_item.h"
 #import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
 #import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_content.h"
+#import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator.h"
 #import "ios/chrome/browser/ui/autofill/cells/card_unmask_header_item.h"
 #import "ui/base/l10n/l10n_util.h"
 
@@ -35,6 +36,7 @@
   OtpInputDialogContent* _content;
   UITableViewDiffableDataSource<NSNumber*, NSNumber*>* _dataSource;
   BOOL _contentSet;
+  NSString* _inputValue;
 }
 
 - (instancetype)init {
@@ -72,12 +74,6 @@
   return view;
 }
 
-#pragma mark - UITextFieldDelegate
-
-- (void)textFieldDidEndEditing:(UITextField*)textField {
-  [self didChangeOtpInputText:textField.text];
-}
-
 #pragma mark - PaymentsSuggestionBottomSheetConsumer
 
 - (void)setContent:(OtpInputDialogContent*)content {
@@ -137,27 +133,34 @@
   [cell setIdentifyingIcon:nil];
   [cell setIcon:TableViewTextEditItemIconTypeEdit];
   cell.textField.placeholder = _content.textFieldPlaceholder;
-  cell.textField.delegate = self;
+  [cell.textField addTarget:self
+                     action:@selector(textFieldDidChange:)
+           forControlEvents:UIControlEventEditingChanged];
   cell.textField.keyboardType = UIKeyboardTypeNumberPad;
   cell.textField.returnKeyType = UIReturnKeyDone;
   cell.textField.textAlignment = NSTextAlignmentLeft;
   return cell;
 }
 
+- (void)textFieldDidChange:(UITextField*)textField {
+  _inputValue = textField.text;
+  [self didChangeOtpInputText];
+}
+
 // Invoked when the confirm button in the navigation bar is tapped by the user.
 // This means a valid OTP value is typed in.
 - (void)didTapConfirmButton {
-  // TODO(crbug.com/324611541): Handle this via a Mutator.
+  [_mutator didTapConfirmButton:_inputValue];
 }
 
 // Invoked when the cancel button in the navigation bar is tapped by the user.
 - (void)didTapCancelButton {
-  // TODO(crbug.com/324611541): Handle this via a Mutator.
+  [_mutator didTapCancelButton];
 }
 
 // Notify the model controller when the OTP input value changes.
-- (void)didChangeOtpInputText:(NSString*)inputValue {
-  // TODO(crbug.com/324611541): Handle this via a Mutator.
+- (void)didChangeOtpInputText {
+  [_mutator onOtpInputChanges:_inputValue];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/autofill/bottom_sheet/virtual_card_enrollment_bottom_sheet_view_controller.mm b/ios/chrome/browser/ui/autofill/bottom_sheet/virtual_card_enrollment_bottom_sheet_view_controller.mm
index 332cd92..7dc9060 100644
--- a/ios/chrome/browser/ui/autofill/bottom_sheet/virtual_card_enrollment_bottom_sheet_view_controller.mm
+++ b/ios/chrome/browser/ui/autofill/bottom_sheet/virtual_card_enrollment_bottom_sheet_view_controller.mm
@@ -25,9 +25,6 @@
 // The padding above and below the illustration image.
 CGFloat const kIllustrationPadding = 20;
 
-// The extra padding on the left and right of the title and explanatory message.
-CGFloat const kTitlePadding = 24;
-
 // The spacing between vertically stacked elements.
 CGFloat const kVerticalSpacingMedium = 16;
 
@@ -139,8 +136,6 @@
   UIStackView* aboveTitleStackView =
       [[UIStackView alloc] initWithFrame:CGRectZero];
   aboveTitleStackView.layoutMarginsRelativeArrangement = YES;
-  aboveTitleStackView.layoutMargins =
-      UIEdgeInsetsMake(0, kTitlePadding, 0, kTitlePadding);
   aboveTitleStackView.axis = UILayoutConstraintAxisVertical;
   aboveTitleStackView.spacing = kVerticalSpacingMedium;
 
@@ -199,7 +194,12 @@
   UILabel* titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
   titleLabel.text = _bottomSheetData.title;
   titleLabel.numberOfLines = 0;  // Allow multiple lines.
-  titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
+  UIFontDescriptor* title2FontDescriptor =
+      [UIFont preferredFontForTextStyle:UIFontTextStyleTitle2].fontDescriptor;
+  titleLabel.font = [UIFont
+      fontWithDescriptor:[title2FontDescriptor fontDescriptorWithSymbolicTraits:
+                                                   UIFontDescriptorTraitBold]
+                    size:0];
   titleLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
   titleLabel.textAlignment = NSTextAlignmentCenter;
   return titleLabel;
@@ -228,9 +228,9 @@
   NSMutableAttributedString* attributedText = [[NSMutableAttributedString alloc]
       initWithString:_bottomSheetData.explanatoryMessage
           attributes:@{
+            NSFontAttributeName :
+                [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote],
             NSParagraphStyleAttributeName : centeredTextStyle,
-            NSForegroundColorAttributeName :
-                [UIColor colorNamed:kTextSecondaryColor]
           }];
   [attributedText addAttribute:NSLinkAttributeName
                          value:@"unused"
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm
index 9e15545..eaddbae9 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm
@@ -28,6 +28,7 @@
 #import "url/gurl.h"
 
 using autofill::CreditCard::RecordType::kVirtualCard;
+using base::SysNSStringToUTF8;
 
 @interface ManualFillCardItem ()
 
@@ -201,16 +202,18 @@
       [self.contentView addSubview:self.virtualCardInstructionTextView];
     }
     self.cardNumberLabeledChip = [[ManualFillLabeledChip alloc]
-        initSingleChipWithSelector:@selector(userDidTapCardNumber:)
-                            target:self];
+        initSingleChipWithTarget:self
+                        selector:@selector(userDidTapCardNumber:)];
     [self.contentView addSubview:self.cardNumberLabeledChip];
+
     self.expirationDateLabeledChip = [[ManualFillLabeledChip alloc]
-        initExpirationDateChipWithSelector:@selector(userDidTapCardInfo:)
-                                    target:self];
+        initExpirationDateChipWithTarget:self
+                           monthSelector:@selector(userDidTapExpirationMonth:)
+                            yearSelector:@selector(userDidTapExpirationYear:)];
     [self.contentView addSubview:self.expirationDateLabeledChip];
     self.cardholderLabeledChip = [[ManualFillLabeledChip alloc]
-        initSingleChipWithSelector:@selector(userDidTapCardInfo:)
-                            target:self];
+        initSingleChipWithTarget:self
+                        selector:@selector(userDidTapCardholderName:)];
     [self.contentView addSubview:self.cardholderLabeledChip];
   } else {
     // TODO(crbug.com/330329960): Deprecate button use once
@@ -401,8 +404,16 @@
                                             requiresHTTPS:YES]) {
     return;
   }
-  base::RecordAction(
-      base::UserMetricsAction("ManualFallback_CreditCard_SelectCardNumber"));
+
+  if (base::FeatureList::IsEnabled(
+          autofill::features::kAutofillEnableVirtualCards)) {
+    base::RecordAction(base::UserMetricsAction(
+        [self createMetricsAction:@"SelectCardNumber"]));
+  } else {
+    base::RecordAction(
+        base::UserMetricsAction("ManualFallback_CreditCard_SelectCardNumber"));
+  }
+
   if (!number.length) {
     [self.navigationDelegate requestFullCreditCard:self.card];
   } else {
@@ -412,27 +423,16 @@
   }
 }
 
+// TODO(crbug.com/330329960): Deprecate this method once
+// kAutofillEnableVirtualCards is enabled.
 - (void)userDidTapCardInfo:(UIButton*)sender {
   const char* metricsAction = nullptr;
-  if (base::FeatureList::IsEnabled(
-          autofill::features::kAutofillEnableVirtualCards)) {
-    if (sender == [self.cardholderLabeledChip singleButton]) {
-      metricsAction = "ManualFallback_CreditCard_SelectCardholderName";
-    } else if (sender ==
-               [self.expirationDateLabeledChip expirationMonthButton]) {
-      metricsAction = "ManualFallback_CreditCard_SelectExpirationMonth";
-    } else if (sender ==
-               [self.expirationDateLabeledChip expirationYearButton]) {
-      metricsAction = "ManualFallback_CreditCard_SelectExpirationYear";
-    }
-  } else {
-    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";
-    }
+  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));
@@ -442,6 +442,39 @@
                              requiresHTTPS:NO];
 }
 
+- (void)userDidTapCardholderName:(UIButton*)sender {
+  base::RecordAction(base::UserMetricsAction(
+      [self createMetricsAction:@"SelectCardholderName"]));
+  [self.contentInjector userDidPickContent:sender.titleLabel.text
+                             passwordField:NO
+                             requiresHTTPS:NO];
+}
+
+- (void)userDidTapExpirationMonth:(UIButton*)sender {
+  base::RecordAction(base::UserMetricsAction(
+      [self createMetricsAction:@"SelectExpirationMonth"]));
+  [self.contentInjector userDidPickContent:sender.titleLabel.text
+                             passwordField:NO
+                             requiresHTTPS:NO];
+}
+
+- (void)userDidTapExpirationYear:(UIButton*)sender {
+  base::RecordAction(base::UserMetricsAction(
+      [self createMetricsAction:@"SelectExpirationYear"]));
+  [self.contentInjector userDidPickContent:sender.titleLabel.text
+                             passwordField:NO
+                             requiresHTTPS:NO];
+}
+
+- (const char*)createMetricsAction:(NSString*)selectedChip {
+  return [NSString stringWithFormat:@"ManualFallback_%@_%@",
+                                    self.card.recordType == kVirtualCard
+                                        ? @"VirtualCard"
+                                        : @"CreditCard",
+                                    selectedChip]
+      .UTF8String;
+}
+
 - (NSString*)createCardName:(ManualFillCreditCard*)card {
   NSString* cardName;
   // TODO: b/322543459 Take out deprecated bank name, add functionality for card
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip.h b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip.h
index 3b00f56..bd7f85e 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip.h
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip.h
@@ -13,13 +13,14 @@
 
 // Creates an ManulFillLabeledChip of 1 UIButton with the given selector and
 // target.
-- (instancetype)initSingleChipWithSelector:(SEL)action
-                                    target:(id)target NS_DESIGNATED_INITIALIZER;
+- (instancetype)initSingleChipWithTarget:(id)target
+                                selector:(SEL)action NS_DESIGNATED_INITIALIZER;
 
 // Creates an ManulFillLabeledChip of 2 UIButtons (separated by a label with the
 // text '/') with the given selector and target.
-- (instancetype)initExpirationDateChipWithSelector:(SEL)action
-                                            target:(id)target
+- (instancetype)initExpirationDateChipWithTarget:(id)target
+                                   monthSelector:(SEL)monthAction
+                                    yearSelector:(SEL)yearAction
     NS_DESIGNATED_INITIALIZER;
 
 - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip.mm b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip.mm
index 061d8b5..b86d60a 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip.mm
@@ -21,7 +21,7 @@
 
 #pragma mark - Public
 
-- (id)initSingleChipWithSelector:(SEL)action target:(id)target {
+- (id)initSingleChipWithTarget:(id)target selector:(SEL)action {
   self = [super initWithFrame:CGRectZero];
   if (self) {
     self.translatesAutoresizingMaskIntoConstraints = NO;
@@ -36,7 +36,9 @@
   return self;
 }
 
-- (id)initExpirationDateChipWithSelector:(SEL)action target:(id)target {
+- (id)initExpirationDateChipWithTarget:(id)target
+                         monthSelector:(SEL)monthAction
+                          yearSelector:(SEL)yearAction {
   self = [super initWithFrame:CGRectZero];
   if (self) {
     self.translatesAutoresizingMaskIntoConstraints = NO;
@@ -45,8 +47,9 @@
 
     _label = CreateLabel();
     [self addArrangedSubview:_label];
-    UIButton* monthButton = CreateChipWithSelectorAndTarget(action, target);
-    UIButton* yearButton = CreateChipWithSelectorAndTarget(action, target);
+    UIButton* monthButton =
+        CreateChipWithSelectorAndTarget(monthAction, target);
+    UIButton* yearButton = CreateChipWithSelectorAndTarget(yearAction, target);
     _buttons = @[ monthButton, yearButton ];
 
     UIStackView* dateStackView = [[UIStackView alloc] initWithFrame:CGRectZero];
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip_unittest.mm b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip_unittest.mm
index 351406b..83a11d00 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip_unittest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_labeled_chip_unittest.mm
@@ -23,7 +23,7 @@
 TEST_F(ManualFillLabeledChipiOSTest, Creation_SingleChip) {
   // Create labeled chip.
   ManualFillLabeledChip* labeledChip =
-      [[ManualFillLabeledChip alloc] initSingleChipWithSelector:0 target:0];
+      [[ManualFillLabeledChip alloc] initSingleChipWithTarget:0 selector:0];
 
   // Confirm there are 2 subviews: a UILabel and a UIButton.
   NSArray<UIView*>* chipSubviews = labeledChip.arrangedSubviews;
@@ -37,8 +37,9 @@
 TEST_F(ManualFillLabeledChipiOSTest, Creation_ExpirationDateChip) {
   // Create labeled chip with a UIButton, a UILabel and another UIButton.
   ManualFillLabeledChip* labeledChip =
-      [[ManualFillLabeledChip alloc] initExpirationDateChipWithSelector:0
-                                                                 target:0];
+      [[ManualFillLabeledChip alloc] initExpirationDateChipWithTarget:0
+                                                        monthSelector:0
+                                                         yearSelector:0];
 
   // Confirm there are 2 subviews: UILabel and UIStackView.
   NSArray<UIView*>* chipSubviews = labeledChip.arrangedSubviews;
@@ -64,7 +65,7 @@
 TEST_F(ManualFillLabeledChipiOSTest, SetText_SingleChip) {
   // Create the labeled chip and populate it with text.
   ManualFillLabeledChip* labeledChip =
-      [[ManualFillLabeledChip alloc] initSingleChipWithSelector:0 target:0];
+      [[ManualFillLabeledChip alloc] initSingleChipWithTarget:0 selector:0];
   [labeledChip setLabelText:TOP_LABEL_TEXT
                buttonTitles:@[ BOTTOM_BUTTON_TEXT_0 ]];
 
@@ -83,8 +84,9 @@
 TEST_F(ManualFillLabeledChipiOSTest, SetText_ExpirationDateChip) {
   // Create labeled chip with a UIButton, a UILabel and another UIButton.
   ManualFillLabeledChip* labeledChip =
-      [[ManualFillLabeledChip alloc] initExpirationDateChipWithSelector:0
-                                                                 target:0];
+      [[ManualFillLabeledChip alloc] initExpirationDateChipWithTarget:0
+                                                        monthSelector:0
+                                                         yearSelector:0];
   [labeledChip setLabelText:TOP_LABEL_TEXT
                buttonTitles:@[ BOTTOM_BUTTON_TEXT_0, BOTTOM_BUTTON_TEXT_2 ]];
 
@@ -110,7 +112,7 @@
 TEST_F(ManualFillLabeledChipiOSTest, PrepareForReuse_SingleChip) {
   // Create the labeled chip and populate it with text.
   ManualFillLabeledChip* labeledChip =
-      [[ManualFillLabeledChip alloc] initSingleChipWithSelector:0 target:0];
+      [[ManualFillLabeledChip alloc] initSingleChipWithTarget:0 selector:0];
   [labeledChip setLabelText:TOP_LABEL_TEXT
                buttonTitles:@[ BOTTOM_BUTTON_TEXT_0 ]];
 
@@ -131,8 +133,9 @@
 TEST_F(ManualFillLabeledChipiOSTest, PrepareForReuse_ExpirationDateChip) {
   // Create labeled chip with a UIButton, a UILabel and another UIButton.
   ManualFillLabeledChip* labeledChip =
-      [[ManualFillLabeledChip alloc] initExpirationDateChipWithSelector:0
-                                                                 target:0];
+      [[ManualFillLabeledChip alloc] initExpirationDateChipWithTarget:0
+                                                        monthSelector:0
+                                                         yearSelector:0];
   [labeledChip setLabelText:TOP_LABEL_TEXT
                buttonTitles:@[ BOTTOM_BUTTON_TEXT_0, BOTTOM_BUTTON_TEXT_2 ]];
 
diff --git a/ios/chrome/browser/ui/settings/password/passwords_mediator.mm b/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
index e95d259..d912ff03 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
@@ -39,6 +39,22 @@
 
 using password_manager::WarningType;
 
+namespace {
+
+// Struct used to count and store the number of active Password Manager widget
+// promos, as the FET does not support showing multiple promos for the same FET
+// feature at the same time.
+struct PasswordManagerActiveWidgetPromoData
+    : public base::SupportsUserData::Data {
+  // The number of active promos.
+  int active_promos = 0;
+
+  // Key to use for this type in SupportsUserData
+  static constexpr char key[] = "PasswordManagerActiveWidgetPromoData";
+};
+
+}  // namespace
+
 @interface PasswordsMediator () <PasswordCheckObserver,
                                  SavedPasswordsPresenterObserver,
                                  SyncObserverModelBridge>
@@ -135,8 +151,7 @@
 
 - (void)disconnect {
   if (_shouldNotifyFETToDismissPasswordManagerWidgetPromo && _tracker) {
-    _tracker->Dismissed(
-        feature_engagement::kIPHiOSPromoPasswordManagerWidgetFeature);
+    [self dismissFETIfNeeded];
   }
   _tracker = nullptr;
   _syncObserver.reset();
@@ -249,8 +264,7 @@
   if (self.tracker) {
     self.tracker->NotifyEvent(
         feature_engagement::events::kPasswordManagerWidgetPromoClosed);
-    self.tracker->Dismissed(
-        feature_engagement::kIPHiOSPromoPasswordManagerWidgetFeature);
+    [self dismissFETIfNeeded];
   }
   _shouldNotifyFETToDismissPasswordManagerWidgetPromo = NO;
 }
@@ -358,15 +372,49 @@
 }
 
 - (BOOL)shouldShowPasswordManagerWidgetPromo {
-  if (self.tracker &&
-      self.tracker->ShouldTriggerHelpUI(
-          feature_engagement::kIPHiOSPromoPasswordManagerWidgetFeature)) {
-    self.shouldNotifyFETToDismissPasswordManagerWidgetPromo = YES;
-    return YES;
+  if (self.tracker) {
+    // First check if another active Password Manager page (e.g. in another
+    // window) has an active promo. If so, just return that the promo should be
+    // shown here without querying the FET. Only query the FET if there is no
+    // currently active promo.
+    PasswordManagerActiveWidgetPromoData* data =
+        static_cast<PasswordManagerActiveWidgetPromoData*>(
+            self.tracker->GetUserData(
+                PasswordManagerActiveWidgetPromoData::key));
+    if (data) {
+      data->active_promos++;
+      self.shouldNotifyFETToDismissPasswordManagerWidgetPromo = YES;
+      return YES;
+    } else if (self.tracker->ShouldTriggerHelpUI(
+                   feature_engagement::
+                       kIPHiOSPromoPasswordManagerWidgetFeature)) {
+      std::unique_ptr<PasswordManagerActiveWidgetPromoData> new_data =
+          std::make_unique<PasswordManagerActiveWidgetPromoData>();
+      new_data->active_promos++;
+      self.tracker->SetUserData(PasswordManagerActiveWidgetPromoData::key,
+                                std::move(new_data));
+      self.shouldNotifyFETToDismissPasswordManagerWidgetPromo = YES;
+      return YES;
+    }
   }
   return NO;
 }
 
+// Check if this is the last active Password Manager showing the widget promo
+// and dismisses the FET if so.
+- (void)dismissFETIfNeeded {
+  PasswordManagerActiveWidgetPromoData* data =
+      static_cast<PasswordManagerActiveWidgetPromoData*>(
+          _tracker->GetUserData(PasswordManagerActiveWidgetPromoData::key));
+  CHECK(data, base::NotFatalUntil::M127);
+  data->active_promos--;
+  if (data->active_promos <= 0) {
+    _tracker->Dismissed(
+        feature_engagement::kIPHiOSPromoPasswordManagerWidgetFeature);
+    _tracker->RemoveUserData(PasswordManagerActiveWidgetPromoData::key);
+  }
+}
+
 #pragma mark - SavedPasswordsPresenterObserver
 
 - (void)savedPasswordsDidChange {
diff --git a/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm b/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
index cd28806..0c374d6 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
@@ -230,7 +230,12 @@
 // Tests that `Dismissed` is called on the FET on disconnect when the Password
 // Manager widget promo was shown and was not dismissed by the user.
 TEST_F(PasswordsMediatorTest, NotifiesFETToDismissPromoOnDisconnect) {
-  mediator().shouldNotifyFETToDismissPasswordManagerWidgetPromo = YES;
+  // Show the promo first.
+  EXPECT_CALL(*mockTracker(), ShouldTriggerHelpUI(testing::_))
+      .WillRepeatedly(testing::Return(true));
+  [mediator() askFETToShowPasswordManagerWidgetPromo];
+
+  ASSERT_TRUE(mediator().shouldNotifyFETToDismissPasswordManagerWidgetPromo);
 
   EXPECT_CALL(
       *mockTracker(),
@@ -244,7 +249,12 @@
 // Tests that `NotifyEvent` and `Dismissed` is called on the FET when the user
 // taps the close button of the Password Manager widget promo.
 TEST_F(PasswordsMediatorTest, NotifiesFETToDismissPromoOnPromoClosed) {
-  mediator().shouldNotifyFETToDismissPasswordManagerWidgetPromo = YES;
+  // Show the promo first.
+  EXPECT_CALL(*mockTracker(), ShouldTriggerHelpUI(testing::_))
+      .WillRepeatedly(testing::Return(true));
+  [mediator() askFETToShowPasswordManagerWidgetPromo];
+
+  ASSERT_TRUE(mediator().shouldNotifyFETToDismissPasswordManagerWidgetPromo);
 
   EXPECT_CALL(
       *mockTracker(),
diff --git a/ios/chrome/browser/ui/snackbar/snackbar_coordinator.mm b/ios/chrome/browser/ui/snackbar/snackbar_coordinator.mm
index df65aa2..c967d502 100644
--- a/ios/chrome/browser/ui/snackbar/snackbar_coordinator.mm
+++ b/ios/chrome/browser/ui/snackbar/snackbar_coordinator.mm
@@ -87,6 +87,14 @@
 
 - (void)showSnackbarMessage:(MDCSnackbarMessage*)message
                bottomOffset:(CGFloat)offset {
+  // TODO:(crbug.com/333567367) Clean up the killswitch.
+  if (base::FeatureList::IsEnabled(kSnackbarUseLegacyDismissalBehavior)) {
+    if ([message
+            respondsToSelector:@selector(setUsesLegacyDismissalBehavior:)]) {
+      message.usesLegacyDismissalBehavior = YES;
+    }
+  }
+
   [[MDCSnackbarManager defaultManager]
       setPresentationHostView:self.baseViewController.view.window];
   [[MDCSnackbarManager defaultManager] setBottomOffset:offset];
@@ -106,14 +114,6 @@
   message.action = action;
   message.completionHandler = completionAction;
 
-  // TODO:(crbug.com/333567367) Clean up the killswitch.
-  if (base::FeatureList::IsEnabled(kSnackbarUseLegacyDismissalBehavior)) {
-    if ([message
-            respondsToSelector:@selector(setUsesLegacyDismissalBehavior:)]) {
-      message.usesLegacyDismissalBehavior = YES;
-    }
-  }
-
   [self showSnackbarMessage:message];
 }
 
diff --git a/ios/chrome/test/providers/content_notification/test_content_notification.mm b/ios/chrome/test/providers/content_notification/test_content_notification.mm
index 56e13a3..9238ccd 100644
--- a/ios/chrome/test/providers/content_notification/test_content_notification.mm
+++ b/ios/chrome/test/providers/content_notification/test_content_notification.mm
@@ -16,6 +16,10 @@
   GURL GetDestinationUrl(NSDictionary<NSString*, id>* payload) final {
     return GURL::EmptyGURL();
   }
+  NSDictionary<NSString*, NSString*>* GetFeedbackPayload(
+      NSDictionary<NSString*, id>* payload) final {
+    return nil;
+  }
 };
 
 }  // anonymous namespace
diff --git a/ios_internal b/ios_internal
index 1c35edf..c448ce7 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 1c35edff45df4af98046ec5a27d8e514dff98e06
+Subproject commit c448ce76c3e84201da5db9ac4a2a579c2fabfdf3
diff --git a/media/audio/win/audio_low_latency_input_win.cc b/media/audio/win/audio_low_latency_input_win.cc
index 18579083..eb498d9 100644
--- a/media/audio/win/audio_low_latency_input_win.cc
+++ b/media/audio/win/audio_low_latency_input_win.cc
@@ -790,8 +790,9 @@
   if (recording && error) {
     // TODO(henrika): perhaps it worth improving the cleanup here by e.g.
     // stopping the audio client, joining the thread etc.?
+    auto saved_last_error = GetLastError();
     NOTREACHED() << "WASAPI capturing failed with error code "
-                 << GetLastError();
+                 << saved_last_error;
   }
 
   // Disable MMCSS.
diff --git a/media/base/supported_types.cc b/media/base/supported_types.cc
index 58801ea..7737099 100644
--- a/media/base/supported_types.cc
+++ b/media/base/supported_types.cc
@@ -315,7 +315,8 @@
 #elif BUILDFLAG(IS_MAC)
   return true;
 #elif BUILDFLAG(IS_WIN)
-  return base::win::GetVersion() >= base::win::Version::WIN11_22H2;
+  return base::win::GetVersion() >= base::win::Version::WIN11_22H2 &&
+         !base::win::OSInfo::GetInstance()->IsWindowsNSku();
 #else
   return false;
 #endif
diff --git a/media/formats/webm/webm_cluster_parser.cc b/media/formats/webm/webm_cluster_parser.cc
index ce4d9399..b04806d 100644
--- a/media/formats/webm/webm_cluster_parser.cc
+++ b/media/formats/webm/webm_cluster_parser.cc
@@ -14,6 +14,7 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/types/optional_util.h"
 #include "media/base/decrypt_config.h"
+#include "media/base/media_client.h"
 #include "media/base/stream_parser_buffer.h"
 #include "media/base/timestamp_constants.h"
 #include "media/base/webvtt_util.h"
@@ -493,12 +494,23 @@
     return false;
   }
 
-  // TODO(wolenetz/acolwell): Validate and use a common cross-parser TrackId
-  // type with remapped bytestream track numbers and allow multiple tracks as
-  // applicable. See https://crbug.com/341581.
-  auto buffer =
-      StreamParserBuffer::CopyFrom(data + data_offset, size - data_offset,
-                                   is_keyframe, buffer_type, track_num);
+  scoped_refptr<StreamParserBuffer> buffer;
+  if (auto* media_client = GetMediaClient()) {
+    if (auto* alloc = media_client->GetMediaAllocator()) {
+      auto data_span = UNSAFE_BUFFERS(base::span(
+          data + data_offset, base::checked_cast<size_t>(size - data_offset)));
+      buffer = StreamParserBuffer::FromExternalMemory(
+          alloc->CopyFrom(data_span), is_keyframe, buffer_type, track_num);
+    }
+  }
+  if (!buffer) {
+    // TODO(wolenetz/acolwell): Validate and use a common cross-parser TrackId
+    // type with remapped bytestream track numbers and allow multiple tracks as
+    // applicable. See https://crbug.com/341581.
+    buffer =
+        StreamParserBuffer::CopyFrom(data + data_offset, size - data_offset,
+                                     is_keyframe, buffer_type, track_num);
+  }
   if (additional_size) {
     buffer->WritableSideData().alpha_data.assign(additional,
                                                  additional + additional_size);
diff --git a/media/gpu/chromeos/oop_video_decoder.cc b/media/gpu/chromeos/oop_video_decoder.cc
index bed8569..b4f6b2a 100644
--- a/media/gpu/chromeos/oop_video_decoder.cc
+++ b/media/gpu/chromeos/oop_video_decoder.cc
@@ -654,7 +654,11 @@
     return;
   }
 
+  // If we change |buffer| to have a fake timestamp, we'll need to restore the
+  // original timestamp in case higher layers rely on that timestamp. The
+  // |buffer_timestamp_restorer| ensures that happens before Decode() returns.
   CHECK(buffer);
+  base::ScopedClosureRunner buffer_timestamp_restorer;
   if (!buffer->end_of_stream()) {
     const base::TimeDelta next_fake_timestamp =
         current_fake_timestamp_ + base::Microseconds(1u);
@@ -669,6 +673,12 @@
         fake_timestamp_to_real_timestamp_cache_.end());
     fake_timestamp_to_real_timestamp_cache_.Put(current_fake_timestamp_,
                                                 buffer->timestamp());
+    buffer_timestamp_restorer.ReplaceClosure(base::BindOnce(
+        [](scoped_refptr<DecoderBuffer> decoder_buffer,
+           base::TimeDelta original_timestamp) {
+          decoder_buffer->set_timestamp(original_timestamp);
+        },
+        buffer, buffer->timestamp()));
     buffer->set_timestamp(current_fake_timestamp_);
   }
 
diff --git a/media/gpu/test/video_player/decoder_listener.cc b/media/gpu/test/video_player/decoder_listener.cc
index d269d61..55fbb86 100644
--- a/media/gpu/test/video_player/decoder_listener.cc
+++ b/media/gpu/test/video_player/decoder_listener.cc
@@ -122,6 +122,12 @@
         times--;
       if (times == 0)
         return true;
+
+      if (received_event == DecoderListener::Event::kFailure) {
+        LOG(ERROR) << "Failed waiting for '" << EventName(sought_event)
+                   << "' event.";
+        return false;
+      }
     }
 
     // Check whether we've exceeded the maximum time we're allowed to wait.
diff --git a/media/gpu/test/video_player/decoder_listener.h b/media/gpu/test/video_player/decoder_listener.h
index 9016c75..c126d171 100644
--- a/media/gpu/test/video_player/decoder_listener.h
+++ b/media/gpu/test/video_player/decoder_listener.h
@@ -47,6 +47,7 @@
     kConfigInfo,  // A config info was encountered in an H.264/HEVC video
                   // stream.
     kNewBuffersRequested,
+    kFailure,
     kNumEvents,
   };
   using EventCallback = base::RepeatingCallback<bool(Event)>;
diff --git a/media/gpu/test/video_player/decoder_wrapper.cc b/media/gpu/test/video_player/decoder_wrapper.cc
index 1c7687c..cbd9f0ae 100644
--- a/media/gpu/test/video_player/decoder_wrapper.cc
+++ b/media/gpu/test/video_player/decoder_wrapper.cc
@@ -341,10 +341,14 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_);
   DCHECK(state_ == DecoderWrapperState::kUninitialized ||
          state_ == DecoderWrapperState::kIdle);
-  ASSERT_TRUE(status.is_ok()) << "Initializing decoder failed";
 
-  state_ = DecoderWrapperState::kIdle;
-  FireEvent(DecoderListener::Event::kInitialized);
+  if (!status.is_ok()) {
+    state_ = DecoderWrapperState::kUninitialized;
+    FireEvent(DecoderListener::Event::kFailure);
+  } else {
+    state_ = DecoderWrapperState::kIdle;
+    FireEvent(DecoderListener::Event::kInitialized);
+  }
 }
 
 void DecoderWrapper::OnDecodeDoneTask(DecoderStatus status) {
diff --git a/media/mojo/clients/mojo_stable_video_decoder_unittest.cc b/media/mojo/clients/mojo_stable_video_decoder_unittest.cc
index 044c309..7fd9ea68 100644
--- a/media/mojo/clients/mojo_stable_video_decoder_unittest.cc
+++ b/media/mojo/clients/mojo_stable_video_decoder_unittest.cc
@@ -472,10 +472,21 @@
   ASSERT_TRUE(endpoints);
 
   constexpr uint8_t kEncodedData[] = {1, 2, 3};
+  constexpr base::TimeDelta kTimestamp = base::Milliseconds(128u);
+  constexpr base::TimeDelta kDuration = base::Milliseconds(16u);
+  constexpr base::TimeDelta kFrontDiscardPadding = base::Milliseconds(2u);
+  constexpr base::TimeDelta kBackDiscardPadding = base::Milliseconds(5u);
+  constexpr bool kIsKeyFrame = true;
+  constexpr uint64_t kSecureHandle = 42;
   scoped_refptr<DecoderBuffer> decoder_buffer_to_send =
       DecoderBuffer::CopyFrom(kEncodedData, std::size(kEncodedData));
   ASSERT_TRUE(decoder_buffer_to_send);
-  decoder_buffer_to_send->WritableSideData().secure_handle = 42;
+  decoder_buffer_to_send->set_timestamp(kTimestamp);
+  decoder_buffer_to_send->set_duration(kDuration);
+  decoder_buffer_to_send->set_discard_padding(
+      std::make_pair(kFrontDiscardPadding, kBackDiscardPadding));
+  decoder_buffer_to_send->set_is_key_frame(kIsKeyFrame);
+  decoder_buffer_to_send->WritableSideData().secure_handle = kSecureHandle;
 
   // First, we'll call MojoStableVideoDecoder::Decode() and store both the
   // DecoderBuffer (without the encoded data) and the Decode() reply callback as
@@ -512,6 +523,14 @@
           &received_decoder_buffer_with_data));
   task_environment_.RunUntilIdle();
   ASSERT_TRUE(received_decoder_buffer_with_data);
+  // We want to check that the |received_decoder_buffer_with_data| matches the
+  // |decoder_buffer_to_send| except for the timestamp: that's because the
+  // OOPVideoDecoder sends DecoderBuffers to the service with fake timestamps.
+  // Hence, before calling MatchesForTesting(), let's restore the real
+  // timestamp.
+  ASSERT_FALSE(received_decoder_buffer_with_data->end_of_stream());
+  EXPECT_NE(received_decoder_buffer_with_data->timestamp(), kTimestamp);
+  received_decoder_buffer_with_data->set_timestamp(kTimestamp);
   EXPECT_TRUE(received_decoder_buffer_with_data->MatchesForTesting(
       *decoder_buffer_to_send));
 
@@ -525,6 +544,26 @@
       });
   std::move(received_decode_cb).Run(kDecoderStatusToReplyWith);
   task_environment_.RunUntilIdle();
+
+  // Note: the VideoDecoder::Decode() API takes a scoped_refptr<DecoderBuffer>
+  // instead of a scoped_refptr<const DecoderBuffer>, so in theory, the
+  // implementation can change the DecoderBuffer in unexpected ways. The
+  // OOPVideoDecoder does change the DecoderBuffer internally, but it should
+  // restore it to its original state before returning from Decode(). The
+  // following expectations test that.
+  EXPECT_EQ(decoder_buffer_to_send->timestamp(), kTimestamp);
+  EXPECT_EQ(decoder_buffer_to_send->duration(), kDuration);
+  EXPECT_EQ(decoder_buffer_to_send->discard_padding().first,
+            kFrontDiscardPadding);
+  EXPECT_EQ(decoder_buffer_to_send->discard_padding().second,
+            kBackDiscardPadding);
+  EXPECT_EQ(decoder_buffer_to_send->is_key_frame(), kIsKeyFrame);
+  ASSERT_EQ(decoder_buffer_to_send->data_size(), std::size(kEncodedData));
+  EXPECT_EQ(base::make_span(decoder_buffer_to_send->data(),
+                            decoder_buffer_to_send->data_size()),
+            base::make_span(kEncodedData, std::size(kEncodedData)));
+  ASSERT_TRUE(decoder_buffer_to_send->side_data().has_value());
+  EXPECT_EQ(decoder_buffer_to_send->side_data()->secure_handle, kSecureHandle);
 }
 
 }  // namespace media
diff --git a/mojo/core/data_pipe_unittest.cc b/mojo/core/data_pipe_unittest.cc
index c31abfb..83ace5a 100644
--- a/mojo/core/data_pipe_unittest.cc
+++ b/mojo/core/data_pipe_unittest.cc
@@ -2375,7 +2375,10 @@
   EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h));
 }
 
-TEST_F(DataPipeTest, StressTestRacyTraps) {
+// Temporarily disabled during experimentation with suppression of this fix for
+// metrics collection only. Re-enable once the experiment is done.
+// See https://crbug.com/41494387.
+TEST_F(DataPipeTest, DISABLED_StressTestRacyTraps) {
   // Regression test for https://crbug.com/1468933. This bug was caused by a
   // race between trap arming and internal data pipe flushes which could result
   // in a data pipe trap appearing to be armed (and thus never re-arming) while
diff --git a/mojo/public/cpp/bindings/lib/message_size_estimator.cc b/mojo/public/cpp/bindings/lib/message_size_estimator.cc
index ff80c7d..1c98b4a 100644
--- a/mojo/public/cpp/bindings/lib/message_size_estimator.cc
+++ b/mojo/public/cpp/bindings/lib/message_size_estimator.cc
@@ -14,24 +14,24 @@
 MessageSizeEstimator::~MessageSizeEstimator() = default;
 
 void MessageSizeEstimator::EnablePredictiveAllocation(uint32_t message_name) {
-  if (message_name >= samples_.size()) {
-    samples_.resize(message_name + 1);
-  }
-  samples_[message_name].emplace(kSampleSize);
-  samples_[message_name]->AddSample(0);
+  auto [it, _] = samples_.insert(
+      {message_name, std::make_unique<SlidingWindow>(kSampleSize)});
+  it->second->AddSample(0);
 }
 
 size_t MessageSizeEstimator::EstimatePayloadSize(uint32_t message_name) const {
-  if (message_name < samples_.size() && samples_[message_name]) {
-    return samples_[message_name]->Max();
+  auto it = samples_.find(message_name);
+  if (it != samples_.end()) {
+    return it->second->Max();
   }
   return 0;
 }
 
 void MessageSizeEstimator::TrackPayloadSize(uint32_t message_name,
                                             size_t size) {
-  if (message_name < samples_.size() && samples_[message_name]) {
-    samples_[message_name]->AddSample(size);
+  auto it = samples_.find(message_name);
+  if (it != samples_.end()) {
+    it->second->AddSample(size);
   }
 }
 
diff --git a/mojo/public/cpp/bindings/lib/message_size_estimator.h b/mojo/public/cpp/bindings/lib/message_size_estimator.h
index 70be833..568c5d5 100644
--- a/mojo/public/cpp/bindings/lib/message_size_estimator.h
+++ b/mojo/public/cpp/bindings/lib/message_size_estimator.h
@@ -8,10 +8,8 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include <optional>
-#include <vector>
-
 #include "base/component_export.h"
+#include "base/containers/flat_map.h"
 #include "base/moving_window.h"
 
 namespace mojo::internal {
@@ -40,7 +38,7 @@
  private:
   using SlidingWindow = base::MovingMax<size_t>;
 
-  std::vector<std::optional<SlidingWindow>> samples_;
+  base::flat_map<uint32_t, std::unique_ptr<SlidingWindow>> samples_;
 };
 
 }  // namespace mojo::internal
diff --git a/mojo/public/cpp/bindings/tests/nullable_value_types_unittest.cc b/mojo/public/cpp/bindings/tests/nullable_value_types_unittest.cc
index ad67f1a..6c7a34f 100644
--- a/mojo/public/cpp/bindings/tests/nullable_value_types_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/nullable_value_types_unittest.cc
@@ -12,6 +12,7 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/tests/nullable_value_types_enums.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/interfaces/bindings/tests/nullable_value_types.mojom-shared.h"
 #include "mojo/public/interfaces/bindings/tests/nullable_value_types.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -433,6 +434,35 @@
         mojom::RegularEnum::kThatValue, TypemappedEnum::kValueOne));
   }
 
+  void MethodWithContainers(
+      const std::vector<std::optional<bool>>& bool_values,
+      const std::vector<std::optional<uint8_t>>& u8_values,
+      const std::vector<std::optional<uint16_t>>& u16_values,
+      const std::vector<std::optional<uint32_t>>& u32_values,
+      const std::vector<std::optional<uint64_t>>& u64_values,
+      const std::vector<std::optional<int8_t>>& i8_values,
+      const std::vector<std::optional<int16_t>>& i16_values,
+      const std::vector<std::optional<int32_t>>& i32_values,
+      const std::vector<std::optional<int64_t>>& i64_values,
+      const std::vector<std::optional<float>>& float_values,
+      const std::vector<std::optional<double>>& double_values,
+      const std::vector<std::optional<mojom::RegularEnum>>& enum_values,
+      const std::vector<std::optional<mojom::ExtensibleEnum>>&
+          extensible_enum_values,
+      const base::flat_map<int32_t, std::optional<bool>>& bool_map,
+      const base::flat_map<int32_t, std::optional<int32_t>>& int_map,
+      MethodWithContainersCallback reply) override {
+    std::move(reply).Run(bool_values, u8_values, u16_values, u32_values,
+                         u64_values, i8_values, i16_values, i32_values,
+                         i64_values, float_values, double_values, enum_values,
+                         extensible_enum_values, bool_map, int_map);
+  }
+
+  void MethodToSendUnknownEnum(MethodToSendUnknownEnumCallback reply) override {
+    std::move(reply).Run(
+        {static_cast<mojom::ExtensibleEnum>(555), std::nullopt});
+  }
+
   const Receiver<mojom::InterfaceV2> receiver_;
   const std::optional<CallerVersion> caller_version_;
 };
diff --git a/mojo/public/interfaces/bindings/tests/nullable_value_types.mojom b/mojo/public/interfaces/bindings/tests/nullable_value_types.mojom
index 19bed624..657b64e 100644
--- a/mojo/public/interfaces/bindings/tests/nullable_value_types.mojom
+++ b/mojo/public/interfaces/bindings/tests/nullable_value_types.mojom
@@ -11,6 +11,11 @@
   kThatValue = 2,
 };
 
+[Extensible]
+enum ExtensibleEnum {
+  [Default] kUnknown = 0,
+};
+
 enum TypemappedEnum {
   kThisOtherValue = 4,
   kThatOtherValue = 8,
@@ -230,5 +235,47 @@
       [MinVersion=1] TypemappedEnum? mapped_enum_value
   );
 
+  // Test method for versioned struct.
   MethodWithVersionedStruct@6(VersionedStructV2 in) => (VersionedStructV2 out);
+
+  // Test method for containers of nullables.
+  MethodWithContainers@7(
+        array<bool?> bool_values,
+        array<uint8?> u8_values,
+        array<uint16?> u16_values,
+        array<uint32?> u32_values,
+        array<uint64?> u64_values,
+        array<int8?> i8_values,
+        array<int16?> i16_values,
+        array<int32?> i32_values,
+        array<int64?> i64_values,
+        array<float?> float_values,
+        array<double?> double_values,
+        array<RegularEnum?> enum_value,
+        array<ExtensibleEnum?> extensible_enum_values,
+        map<int32, bool?> bool_map,
+        map<int32, int32?> int_map
+  ) => (
+        array<bool?> bool_values,
+        array<uint8?> u8_values,
+        array<uint16?> u16_values,
+        array<uint32?> u32_values,
+        array<uint64?> u64_values,
+        array<int8?> i8_values,
+        array<int16?> i16_values,
+        array<int32?> i32_values,
+        array<int64?> i64_values,
+        array<float?> float_values,
+        array<double?> double_values,
+        array<RegularEnum?> enum_value,
+        array<ExtensibleEnum?> extensible_enum_values,
+        map<int32, bool?> bool_map,
+        map<int32, int32?> int_map
+  );
+
+  // C++ implementation will purposely send an unknown enum to exercise Java
+  // unknown enum handling.
+  // Result should be an array of a single unknown enum value followed by a null
+  // value, i.e.: {[unknown enum value], null}.
+  MethodToSendUnknownEnum@8() => (array<ExtensibleEnum?> out);
 };
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java
index f30b395..522334d 100644
--- a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java
@@ -97,6 +97,13 @@
         return res;
     }
 
+    /** Computes the size needed to represent |numElements|, aligned to |alignmentBytes|. */
+    public static int computeBitfieldSize(long alignmentBytes, int numElements) {
+        // Math must be in long to ensure that we do not overflow.
+        long alignmentBits = alignmentBytes * 8;
+        return (int) (((numElements + alignmentBits - 1) / alignmentBits) * alignmentBytes);
+    }
+
     /** Returns |true| if and only if the two objects are equals, handling |null|. */
     public static boolean equals(Object o1, Object o2) {
         if (o1 == o2) {
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java
index b8f8ba88e..5e543b4 100644
--- a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java
@@ -12,6 +12,7 @@
 import org.chromium.mojo.system.SharedBufferHandle;
 import org.chromium.mojo.system.UntypedHandle;
 
+import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.charset.Charset;
 
@@ -194,7 +195,7 @@
      * array where elements are pointers.
      */
     public DataHeader readDataHeaderForPointerArray(int expectedLength) {
-        return readDataHeaderForArray(BindingsHelper.POINTER_SIZE, expectedLength);
+        return readDataHeaderForArray(BindingsHelper.POINTER_SIZE, expectedLength, false);
     }
 
     /**
@@ -202,7 +203,7 @@
      * array where elements are unions.
      */
     public DataHeader readDataHeaderForUnionArray(int expectedLength) {
-        return readDataHeaderForArray(BindingsHelper.UNION_SIZE, expectedLength);
+        return readDataHeaderForArray(BindingsHelper.UNION_SIZE, expectedLength, false);
     }
 
     /**
@@ -286,7 +287,7 @@
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForBooleanArray(expectedLength);
+        DataHeader si = d.readDataHeaderForBooleanArray(expectedLength, false);
         byte[] bytes = new byte[(si.elementsOrVersion + 7) / BindingsHelper.ALIGNMENT];
         d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
         d.mMessage.getData().get(bytes);
@@ -302,84 +303,250 @@
         return result;
     }
 
+    /** Deserializes an array of Booleans at the given offset. */
+    public Boolean[] readBooleanNullables(int offset, int arrayNullability, int expectedLength) {
+        Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+        if (d == null) {
+            return null;
+        }
+        DataHeader si = d.readDataHeaderForBooleanArray(expectedLength, true);
+        d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+
+        boolean[] hasValueBitfield = readBitfield(1, si.elementsOrVersion, d.mMessage.getData());
+        boolean[] values = readBitfield(1, si.elementsOrVersion, d.mMessage.getData());
+
+        Boolean[] result = new Boolean[si.elementsOrVersion];
+        for (int i = 0; i < si.elementsOrVersion; ++i) {
+            if (hasValueBitfield[i]) {
+                result[i] = values[i];
+            } else {
+                result[i] = null;
+            }
+        }
+        return result;
+    }
+
     /** Deserializes an array of bytes at the given offset. */
     public byte[] readBytes(int offset, int arrayNullability, int expectedLength) {
         Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(1, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(1, expectedLength, false);
         byte[] result = new byte[si.elementsOrVersion];
         d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
         d.mMessage.getData().get(result);
         return result;
     }
 
+    /** Deserializes an array of Bytes at the given offset. */
+    public Byte[] readByteNullables(int offset, int arrayNullability, int expectedLength) {
+        Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+        if (d == null) {
+            return null;
+        }
+        DataHeader si = d.readDataHeaderForArray(1, expectedLength, true);
+        d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+
+        boolean[] hasValueBitfield = readBitfield(1, si.elementsOrVersion, d.mMessage.getData());
+        byte[] values = new byte[si.elementsOrVersion];
+        d.mMessage.getData().get(values);
+
+        Byte[] result = new Byte[si.elementsOrVersion];
+        for (int i = 0; i < si.elementsOrVersion; ++i) {
+            if (hasValueBitfield[i]) {
+                result[i] = values[i];
+            } else {
+                result[i] = null;
+            }
+        }
+        return result;
+    }
+
     /** Deserializes an array of shorts at the given offset. */
     public short[] readShorts(int offset, int arrayNullability, int expectedLength) {
         Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(2, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(2, expectedLength, false);
         short[] result = new short[si.elementsOrVersion];
         d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
         d.mMessage.getData().asShortBuffer().get(result);
         return result;
     }
 
+    /** Deserializes an array of Shorts at the given offset. */
+    public Short[] readShortNullables(int offset, int arrayNullability, int expectedLength) {
+        Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+        if (d == null) {
+            return null;
+        }
+        DataHeader si = d.readDataHeaderForArray(2, expectedLength, true);
+        d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+
+        boolean[] hasValueBitfield = readBitfield(2, si.elementsOrVersion, d.mMessage.getData());
+        short[] values = new short[si.elementsOrVersion];
+        d.mMessage.getData().asShortBuffer().get(values);
+
+        Short[] result = new Short[si.elementsOrVersion];
+        for (int i = 0; i < si.elementsOrVersion; ++i) {
+            if (hasValueBitfield[i]) {
+                result[i] = values[i];
+            } else {
+                result[i] = null;
+            }
+        }
+        return result;
+    }
+
     /** Deserializes an array of ints at the given offset. */
     public int[] readInts(int offset, int arrayNullability, int expectedLength) {
         Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(4, expectedLength, false);
         int[] result = new int[si.elementsOrVersion];
         d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
         d.mMessage.getData().asIntBuffer().get(result);
         return result;
     }
 
+    /** Deserializes an array of Integers at the given offset. */
+    public Integer[] readIntNullables(int offset, int arrayNullability, int expectedLength) {
+        Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+        if (d == null) {
+            return null;
+        }
+        DataHeader si = d.readDataHeaderForArray(4, expectedLength, true);
+        d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+
+        boolean[] hasValueBitfield = readBitfield(4, si.elementsOrVersion, d.mMessage.getData());
+        int[] values = new int[si.elementsOrVersion];
+        d.mMessage.getData().asIntBuffer().get(values);
+
+        Integer[] result = new Integer[si.elementsOrVersion];
+        for (int i = 0; i < si.elementsOrVersion; ++i) {
+            if (hasValueBitfield[i]) {
+                result[i] = values[i];
+            } else {
+                result[i] = null;
+            }
+        }
+        return result;
+    }
+
     /** Deserializes an array of floats at the given offset. */
     public float[] readFloats(int offset, int arrayNullability, int expectedLength) {
         Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(4, expectedLength, false);
         float[] result = new float[si.elementsOrVersion];
         d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
         d.mMessage.getData().asFloatBuffer().get(result);
         return result;
     }
 
+    /** Deserializes an array of Integers at the given offset. */
+    public Float[] readFloatNullables(int offset, int arrayNullability, int expectedLength) {
+        Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+        if (d == null) {
+            return null;
+        }
+        DataHeader si = d.readDataHeaderForArray(4, expectedLength, true);
+        d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+
+        boolean[] hasValueBitfield = readBitfield(4, si.elementsOrVersion, d.mMessage.getData());
+        float[] values = new float[si.elementsOrVersion];
+        d.mMessage.getData().asFloatBuffer().get(values);
+
+        Float[] result = new Float[si.elementsOrVersion];
+        for (int i = 0; i < si.elementsOrVersion; ++i) {
+            if (hasValueBitfield[i]) {
+                result[i] = values[i];
+            } else {
+                result[i] = null;
+            }
+        }
+        return result;
+    }
+
     /** Deserializes an array of longs at the given offset. */
     public long[] readLongs(int offset, int arrayNullability, int expectedLength) {
         Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(8, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(8, expectedLength, false);
         long[] result = new long[si.elementsOrVersion];
         d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
         d.mMessage.getData().asLongBuffer().get(result);
         return result;
     }
 
+    /** Deserializes an array of Longs at the given offset. */
+    public Long[] readLongNullables(int offset, int arrayNullability, int expectedLength) {
+        Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+        if (d == null) {
+            return null;
+        }
+        DataHeader si = d.readDataHeaderForArray(8, expectedLength, true);
+        d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+
+        boolean[] hasValueBitfield = readBitfield(8, si.elementsOrVersion, d.mMessage.getData());
+        long[] values = new long[si.elementsOrVersion];
+        d.mMessage.getData().asLongBuffer().get(values);
+
+        Long[] result = new Long[si.elementsOrVersion];
+        for (int i = 0; i < si.elementsOrVersion; ++i) {
+            if (hasValueBitfield[i]) {
+                result[i] = values[i];
+            } else {
+                result[i] = null;
+            }
+        }
+        return result;
+    }
+
     /** Deserializes an array of doubles at the given offset. */
     public double[] readDoubles(int offset, int arrayNullability, int expectedLength) {
         Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(8, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(8, expectedLength, false);
         double[] result = new double[si.elementsOrVersion];
         d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
         d.mMessage.getData().asDoubleBuffer().get(result);
         return result;
     }
 
+    /** Deserializes an array of Doubles at the given offset. */
+    public Double[] readDoubleNullables(int offset, int arrayNullability, int expectedLength) {
+        Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+        if (d == null) {
+            return null;
+        }
+        DataHeader si = d.readDataHeaderForArray(8, expectedLength, true);
+        d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+
+        boolean[] hasValueBitfield = readBitfield(8, si.elementsOrVersion, d.mMessage.getData());
+        double[] values = new double[si.elementsOrVersion];
+        d.mMessage.getData().asDoubleBuffer().get(values);
+        Double[] result = new Double[si.elementsOrVersion];
+        for (int i = 0; i < si.elementsOrVersion; ++i) {
+            if (hasValueBitfield[i]) {
+                result[i] = values[i];
+            } else {
+                result[i] = null;
+            }
+        }
+        return result;
+    }
+
     /** Deserializes an |Handle| at the given offset. */
     public Handle readHandle(int offset, boolean nullable) {
         int index = readInt(offset);
@@ -472,7 +639,7 @@
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(4, expectedLength, false);
         Handle[] result = new Handle[si.elementsOrVersion];
         for (int i = 0; i < result.length; ++i) {
             result[i] =
@@ -490,7 +657,7 @@
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(4, expectedLength, false);
         UntypedHandle[] result = new UntypedHandle[si.elementsOrVersion];
         for (int i = 0; i < result.length; ++i) {
             result[i] =
@@ -508,7 +675,7 @@
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(4, expectedLength, false);
         DataPipe.ConsumerHandle[] result = new DataPipe.ConsumerHandle[si.elementsOrVersion];
         for (int i = 0; i < result.length; ++i) {
             result[i] =
@@ -526,7 +693,7 @@
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(4, expectedLength, false);
         DataPipe.ProducerHandle[] result = new DataPipe.ProducerHandle[si.elementsOrVersion];
         for (int i = 0; i < result.length; ++i) {
             result[i] =
@@ -544,7 +711,7 @@
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(4, expectedLength, false);
         MessagePipeHandle[] result = new MessagePipeHandle[si.elementsOrVersion];
         for (int i = 0; i < result.length; ++i) {
             result[i] =
@@ -562,7 +729,7 @@
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(4, expectedLength, false);
         SharedBufferHandle[] result = new SharedBufferHandle[si.elementsOrVersion];
         for (int i = 0; i < result.length; ++i) {
             result[i] =
@@ -581,7 +748,8 @@
             return null;
         }
         DataHeader si =
-                d.readDataHeaderForArray(BindingsHelper.SERIALIZED_INTERFACE_SIZE, expectedLength);
+                d.readDataHeaderForArray(
+                        BindingsHelper.SERIALIZED_INTERFACE_SIZE, expectedLength, false);
         S[] result = manager.buildArray(si.elementsOrVersion);
         for (int i = 0; i < result.length; ++i) {
             // This cast is necessary because java 6 doesn't handle wildcard correctly when using
@@ -606,7 +774,7 @@
         if (d == null) {
             return null;
         }
-        DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+        DataHeader si = d.readDataHeaderForArray(4, expectedLength, false);
         @SuppressWarnings("unchecked")
         InterfaceRequest<I>[] result = new InterfaceRequest[si.elementsOrVersion];
         for (int i = 0; i < result.length; ++i) {
@@ -642,9 +810,18 @@
      * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an
      * array of booleans.
      */
-    private DataHeader readDataHeaderForBooleanArray(int expectedLength) {
+    private DataHeader readDataHeaderForBooleanArray(
+            long expectedLength, boolean containsHasValueBitfield) {
         DataHeader dataHeader = readDataHeader();
-        if (dataHeader.size < DataHeader.HEADER_SIZE + (dataHeader.elementsOrVersion + 7) / 8) {
+
+        long packedBoolSize = (dataHeader.elementsOrVersion + 7) / 8;
+        long expectedSize = DataHeader.HEADER_SIZE + packedBoolSize;
+        if (containsHasValueBitfield) {
+            long hasValueBitfieldSize = packedBoolSize;
+            expectedSize += hasValueBitfieldSize;
+        }
+
+        if (dataHeader.size < expectedSize) {
             throw new DeserializationException("Array header is incorrect.");
         }
         if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH
@@ -660,10 +837,18 @@
     }
 
     /** Deserializes a {@link DataHeader} of an array at the given offset. */
-    private DataHeader readDataHeaderForArray(long elementSize, int expectedLength) {
+    private DataHeader readDataHeaderForArray(
+            long elementSize, int expectedLength, boolean containsHasValueBitfield) {
         DataHeader dataHeader = readDataHeader();
-        if (dataHeader.size
-                < (DataHeader.HEADER_SIZE + elementSize * dataHeader.elementsOrVersion)) {
+
+        long totalElementsSize = elementSize * dataHeader.elementsOrVersion;
+        long expectedSize = DataHeader.HEADER_SIZE + totalElementsSize;
+        if (containsHasValueBitfield) {
+            expectedSize +=
+                    BindingsHelper.computeBitfieldSize(elementSize, dataHeader.elementsOrVersion);
+        }
+
+        if (dataHeader.size < expectedSize) {
             throw new DeserializationException("Array header is incorrect.");
         }
         if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH
@@ -678,6 +863,20 @@
         return dataHeader;
     }
 
+    private static boolean[] readBitfield(int typeSize, int numElements, ByteBuffer buffer) {
+        boolean[] bitfield = new boolean[numElements];
+
+        byte[] b = new byte[BindingsHelper.computeBitfieldSize(typeSize, numElements)];
+        buffer.get(b);
+
+        for (int i = 0; i < numElements; ++i) {
+            int idx = i / 8;
+            int mask = 1 << (i % 8);
+            bitfield[i] = (b[idx] & mask) != 0;
+        }
+        return bitfield;
+    }
+
     private void validateBufferSize(int offset, int size) {
         if (mMessage.getData().limit() < offset + size) {
             throw new DeserializationException("Buffer is smaller than expected.");
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java
index a365c0b..8920319 100644
--- a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java
@@ -272,6 +272,45 @@
         return encoderForArray(BindingsHelper.UNION_SIZE, length, offset, expectedLength);
     }
 
+    /**
+     * Computes the packed bitfield which signals whether or not an element at a certain index has
+     * value or should be considered unset.
+     *
+     * @param arr The array. The bit for an index will be set if the value is not null, otherwise it
+     *     will have a zero value.
+     * @param alignmentBytes The alignment for the arr data type. This determines how much padding
+     *     is needed to keep alignment. For example, if alignmentByte is 4-bytes and only 2 bytes
+     *     are needed to represent the bitfield, then another 2 bytes of padding will be added to
+     *     maintain alignment.
+     */
+    public static byte[] computeHasValueBitfieldForArray(Object[] arr, int alignmentBytes) {
+        boolean[] nullMap = new boolean[arr.length];
+        for (int i = 0; i < arr.length; ++i) {
+            nullMap[i] = arr[i] != null;
+        }
+        return packBoolsToBitfield(nullMap, alignmentBytes);
+    }
+
+    /**
+     * Packs a boolean array to bitfield so that only 1 bit is needed to represent a bool value.
+     *
+     * @param bool the boolean array.
+     * @param alignmentBytes Determines the width of the bitfield. If a bitfield size is not a
+     *     multiple of alignmentBytes, additional padding will be added so that the final size is a
+     *     multiple of alignmentBytes.
+     */
+    private static byte[] packBoolsToBitfield(boolean[] bools, int alignmentBytes) {
+        byte[] bytes = new byte[BindingsHelper.computeBitfieldSize(alignmentBytes, bools.length)];
+        for (int i = 0; i < bools.length; ++i) {
+            if (bools[i]) {
+                int idx = i / 8;
+                int mask = i % 8;
+                bytes[idx] |= (byte) (1 << mask);
+            }
+        }
+        return bytes;
+    }
+
     /** Encodes an array of booleans. */
     public void encode(boolean[] v, int offset, int arrayNullability, int expectedLength) {
         if (v == null) {
@@ -282,20 +321,14 @@
                 && expectedLength != v.length) {
             throw new SerializationException("Trying to encode a fixed array of incorrect length.");
         }
-        byte[] bytes = new byte[(v.length + 7) / BindingsHelper.ALIGNMENT];
-        for (int i = 0; i < bytes.length; ++i) {
-            for (int j = 0; j < BindingsHelper.ALIGNMENT; ++j) {
-                int booleanIndex = BindingsHelper.ALIGNMENT * i + j;
-                if (booleanIndex < v.length && v[booleanIndex]) {
-                    bytes[i] |= (byte) (1 << j);
-                }
-            }
-        }
-        encodeByteArray(bytes, v.length, offset);
+        byte[] bytes = packBoolsToBitfield(v, 1);
+        Encoder encoder = encoderForArrayByTotalSize(bytes.length, v.length, offset);
+
+        encoder.mEncoderState.byteBuffer.position(encoder.mBaseOffset + DataHeader.HEADER_SIZE);
+        encoder.append(bytes);
     }
 
-    /** Encodes an array of bytes. */
-    public void encode(byte[] v, int offset, int arrayNullability, int expectedLength) {
+    public void encode(Boolean[] v, int offset, int arrayNullability, int expectedLength) {
         if (v == null) {
             encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
             return;
@@ -304,7 +337,42 @@
                 && expectedLength != v.length) {
             throw new SerializationException("Trying to encode a fixed array of incorrect length.");
         }
-        encodeByteArray(v, v.length, offset);
+        byte[] hasValueBitfield = computeHasValueBitfieldForArray(v, 1);
+
+        boolean[] unboxed = new boolean[v.length];
+        for (int i = 0; i < v.length; ++i) {
+            if (v[i] != null) {
+                unboxed[i] = v[i].booleanValue();
+            } else {
+                unboxed[i] = false;
+            }
+        }
+        byte[] packed = packBoolsToBitfield(unboxed, 1);
+
+        Encoder encoder =
+                encoderForArrayByTotalSize(
+                        hasValueBitfield.length + packed.length, v.length, offset);
+        encoder.mEncoderState.byteBuffer.position(encoder.mBaseOffset + DataHeader.HEADER_SIZE);
+        encoder.append(hasValueBitfield);
+        encoder.append(packed);
+    }
+
+    /** Encodes an array of bytes. */
+    public void encode(byte[] v, int offset, int arrayNullability, int expectedLength) {
+        if (v == null) {
+            encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+            return;
+        }
+        encoderForArrayOfElements(1, v.length, offset, expectedLength).append(v);
+    }
+
+    /** Encodes an array of bytes. */
+    public void encode(Byte[] v, int offset, int arrayNullability, int expectedLength) {
+        if (v == null) {
+            encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+            return;
+        }
+        encoderForNullablePrimitives(1, v, offset, expectedLength).append(v);
     }
 
     /** Encodes an array of shorts. */
@@ -313,7 +381,16 @@
             encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
             return;
         }
-        encoderForArray(2, v.length, offset, expectedLength).append(v);
+        encoderForArrayOfElements(2, v.length, offset, expectedLength).append(v);
+    }
+
+    /** Encodes an array of shorts. */
+    public void encode(Short[] v, int offset, int arrayNullability, int expectedLength) {
+        if (v == null) {
+            encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+            return;
+        }
+        encoderForNullablePrimitives(2, v, offset, expectedLength).append(v);
     }
 
     /** Encodes an array of ints. */
@@ -322,7 +399,16 @@
             encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
             return;
         }
-        encoderForArray(4, v.length, offset, expectedLength).append(v);
+        encoderForArrayOfElements(4, v.length, offset, expectedLength).append(v);
+    }
+
+    /** Encodes an array of Integers. */
+    public void encode(Integer[] v, int offset, int arrayNullability, int expectedLength) {
+        if (v == null) {
+            encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+            return;
+        }
+        encoderForNullablePrimitives(4, v, offset, expectedLength).append(v);
     }
 
     /** Encodes an array of floats. */
@@ -331,7 +417,16 @@
             encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
             return;
         }
-        encoderForArray(4, v.length, offset, expectedLength).append(v);
+        encoderForArrayOfElements(4, v.length, offset, expectedLength).append(v);
+    }
+
+    /** Encodes an array of floats. */
+    public void encode(Float[] v, int offset, int arrayNullability, int expectedLength) {
+        if (v == null) {
+            encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+            return;
+        }
+        encoderForNullablePrimitives(4, v, offset, expectedLength).append(v);
     }
 
     /** Encodes an array of longs. */
@@ -340,7 +435,16 @@
             encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
             return;
         }
-        encoderForArray(8, v.length, offset, expectedLength).append(v);
+        encoderForArrayOfElements(8, v.length, offset, expectedLength).append(v);
+    }
+
+    /** Encodes an array of longs. */
+    public void encode(Long[] v, int offset, int arrayNullability, int expectedLength) {
+        if (v == null) {
+            encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+            return;
+        }
+        encoderForNullablePrimitives(8, v, offset, expectedLength).append(v);
     }
 
     /** Encodes an array of doubles. */
@@ -349,7 +453,16 @@
             encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
             return;
         }
-        encoderForArray(8, v.length, offset, expectedLength).append(v);
+        encoderForArrayOfElements(8, v.length, offset, expectedLength).append(v);
+    }
+
+    /** Encodes an array of doubles. */
+    public void encode(Double[] v, int offset, int arrayNullability, int expectedLength) {
+        if (v == null) {
+            encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+            return;
+        }
+        encoderForNullablePrimitives(8, v, offset, expectedLength).append(v);
     }
 
     /** Encodes an array of {@link Handle}. */
@@ -470,6 +583,38 @@
         encode((long) mEncoderState.dataEnd - (mBaseOffset + offset), offset);
     }
 
+    /**
+     * Returns an encoder which is ready to accept an array of nullable elements. Users should not
+     * advance the byte buffer to account for the header.
+     */
+    private Encoder encoderForNullablePrimitives(
+            int elementSizeInByte, Object[] values, int offset, int expectedLength) {
+        if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH
+                && expectedLength != values.length) {
+            throw new SerializationException("Trying to encode a fixed array of incorrect length.");
+        }
+        byte[] bitField = computeHasValueBitfieldForArray(values, elementSizeInByte);
+        Encoder encoder =
+                encoderForArrayByTotalSize(
+                        (values.length * elementSizeInByte) + bitField.length,
+                        values.length,
+                        offset);
+        encoder.mEncoderState.byteBuffer.position(encoder.mBaseOffset + DataHeader.HEADER_SIZE);
+        encoder.mEncoderState.byteBuffer.put(bitField);
+        return encoder;
+    }
+
+    /**
+     * Returns an encoder which is ready to accept an array of elements. Users should not advance
+     * the byte buffer to account for the header.
+     */
+    private Encoder encoderForArrayOfElements(
+            int elementSizeInByte, int length, int offset, int expectedLength) {
+        Encoder encoder = encoderForArray(elementSizeInByte, length, offset, expectedLength);
+        encoder.mEncoderState.byteBuffer.position(encoder.mBaseOffset + DataHeader.HEADER_SIZE);
+        return encoder;
+    }
+
     private Encoder encoderForArray(
             int elementSizeInByte, int length, int offset, int expectedLength) {
         if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH && expectedLength != length) {
@@ -483,37 +628,87 @@
         return getEncoderAtDataOffset(new DataHeader(DataHeader.HEADER_SIZE + byteSize, length));
     }
 
-    private void encodeByteArray(byte[] bytes, int length, int offset) {
-        encoderForArrayByTotalSize(bytes.length, length, offset).append(bytes);
-    }
-
     private void append(byte[] v) {
-        mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
         mEncoderState.byteBuffer.put(v);
     }
 
+    private void append(Byte[] values) {
+        for (Byte b : values) {
+            if (b != null) {
+                mEncoderState.byteBuffer.put(b);
+            } else {
+                mEncoderState.byteBuffer.put((byte) 0);
+            }
+        }
+    }
+
     private void append(short[] v) {
-        mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
         mEncoderState.byteBuffer.asShortBuffer().put(v);
     }
 
+    private void append(Short[] values) {
+        for (Short s : values) {
+            if (s != null) {
+                mEncoderState.byteBuffer.putShort(s);
+            } else {
+                mEncoderState.byteBuffer.putShort((short) 0);
+            }
+        }
+    }
+
     private void append(int[] v) {
-        mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
         mEncoderState.byteBuffer.asIntBuffer().put(v);
     }
 
+    private void append(Integer[] values) {
+        for (Integer i : values) {
+            if (i != null) {
+                mEncoderState.byteBuffer.putInt(i);
+            } else {
+                mEncoderState.byteBuffer.putInt(0);
+            }
+        }
+    }
+
     private void append(float[] v) {
-        mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
         mEncoderState.byteBuffer.asFloatBuffer().put(v);
     }
 
+    private void append(Float[] values) {
+        for (Float f : values) {
+            if (f != null) {
+                mEncoderState.byteBuffer.putFloat(f);
+            } else {
+                mEncoderState.byteBuffer.putFloat(0.0f);
+            }
+        }
+    }
+
     private void append(double[] v) {
-        mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
         mEncoderState.byteBuffer.asDoubleBuffer().put(v);
     }
 
+    private void append(Double[] values) {
+        for (Double d : values) {
+            if (d != null) {
+                mEncoderState.byteBuffer.putDouble(d);
+            } else {
+                mEncoderState.byteBuffer.putDouble(0.0f);
+            }
+        }
+    }
+
     private void append(long[] v) {
-        mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
         mEncoderState.byteBuffer.asLongBuffer().put(v);
     }
+
+    private void append(Long[] values) {
+        for (Long v : values) {
+            if (v != null) {
+                mEncoderState.byteBuffer.putLong(v);
+            } else {
+                mEncoderState.byteBuffer.putLong(0);
+            }
+        }
+    }
 }
diff --git a/mojo/public/java/system/javatests/nullable_value_types_test_util.cc b/mojo/public/java/system/javatests/nullable_value_types_test_util.cc
index f095da6..aa49e62 100644
--- a/mojo/public/java/system/javatests/nullable_value_types_test_util.cc
+++ b/mojo/public/java/system/javatests/nullable_value_types_test_util.cc
@@ -80,6 +80,37 @@
     // Not currently exercised by tests.
     NOTREACHED_NORETURN();
   }
+
+  void MethodWithContainers(
+      const std::vector<std::optional<bool>>& bool_values,
+      const std::vector<std::optional<uint8_t>>& u8_values,
+      const std::vector<std::optional<uint16_t>>& u16_values,
+      const std::vector<std::optional<uint32_t>>& u32_values,
+      const std::vector<std::optional<uint64_t>>& u64_values,
+      const std::vector<std::optional<int8_t>>& i8_values,
+      const std::vector<std::optional<int16_t>>& i16_values,
+      const std::vector<std::optional<int32_t>>& i32_values,
+      const std::vector<std::optional<int64_t>>& i64_values,
+      const std::vector<std::optional<float>>& float_values,
+      const std::vector<std::optional<double>>& double_values,
+      const std::vector<std::optional<mojom::RegularEnum>>& enum_values,
+      const std::vector<std::optional<mojom::ExtensibleEnum>>&
+          extensible_enum_values,
+      const base::flat_map<int32_t, std::optional<bool>>& bool_map,
+      const base::flat_map<int32_t, std::optional<int32_t>>& int_map,
+      MethodWithContainersCallback reply) override {
+    std::move(reply).Run(bool_values, u8_values, u16_values, u32_values,
+                         u64_values, i8_values, i16_values, i32_values,
+                         i64_values, float_values, double_values, enum_values,
+                         extensible_enum_values, bool_map, int_map);
+  }
+
+  void MethodToSendUnknownEnum(MethodToSendUnknownEnumCallback reply) override {
+    std::move(reply).Run(std::vector<std::optional<mojom::ExtensibleEnum>>{
+        static_cast<mojom::ExtensibleEnum>(
+            555),  // intentionally an unknown enum value.
+        std::nullopt});
+  }
 };
 
 }  // namespace
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NullableValueTypesTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NullableValueTypesTest.java
index 6370622f..a05dbb5 100644
--- a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NullableValueTypesTest.java
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NullableValueTypesTest.java
@@ -15,6 +15,7 @@
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.mojo.MojoTestRule;
+import org.chromium.mojo.bindings.test.mojom.nullable_value_types.ExtensibleEnum;
 import org.chromium.mojo.bindings.test.mojom.nullable_value_types.InterfaceV2;
 import org.chromium.mojo.bindings.test.mojom.nullable_value_types.RegularEnum;
 import org.chromium.mojo.bindings.test.mojom.nullable_value_types.StructWithEnums;
@@ -23,6 +24,9 @@
 import org.chromium.mojo.system.Pair;
 import org.chromium.mojo.system.impl.CoreImpl;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Testing the Java bindings implementation for nullable value types, e.g. `int32?`. Note that the
  * Java tests are not exhaustive; it is assumed that successful interop with a C++ implementation
@@ -289,6 +293,148 @@
             mTestRule.runLoopForever();
         }
 
+        {
+            // No specific pattern to the tests. We just want a mixture of values and nulls.
+            Boolean[] boolValues = {true, true, true, null, false, false, false};
+            Byte[] uByteValues = {null, (byte) 8, null};
+            Short[] uShortValues = {null, (short) 1, (short) 6, null};
+            Integer[] uIntValues = {null, 3, 2, null};
+            Long[] uLongValues = {null, 6L, 4L, null};
+            Byte[] byteValues = {null, (byte) 3, (byte) 2, null};
+            Short[] shortValues = {null, (short) 3, (short) 2, null};
+            Integer[] intValues = {null, 3, 2, null};
+            Long[] longValues = {null, 3L, 2L, null};
+            Float[] floatValues = {null, 4.0f, 2.0f, null};
+            Double[] doubleValues = {null, 6.0, 4.0, null};
+            @RegularEnum.EnumType Integer[] enumValues = {RegularEnum.THIS_VALUE, null};
+            // Explicitly test defaulting behaviour.
+            @ExtensibleEnum.EnumType
+            Integer[] extensibleEnumValues = {null, 555, ExtensibleEnum.UNKNOWN};
+            Map<Integer, Boolean> boolMap = new HashMap();
+            boolMap.put(0, true);
+            boolMap.put(1, null);
+            boolMap.put(2, false);
+            Map<Integer, Integer> intMap = new HashMap();
+            intMap.put(0, 10);
+            intMap.put(1, null);
+            intMap.put(2, 12);
+
+            remote.methodWithContainers(
+                    boolValues,
+                    uByteValues,
+                    uShortValues,
+                    uIntValues,
+                    uLongValues,
+                    byteValues,
+                    shortValues,
+                    intValues,
+                    longValues,
+                    floatValues,
+                    doubleValues,
+                    enumValues,
+                    extensibleEnumValues,
+                    boolMap,
+                    intMap,
+                    (Boolean[] outBoolValues,
+                            Byte[] outUByteValues,
+                            Short[] outUShortValues,
+                            Integer[] outUIntValues,
+                            Long[] outULongValues,
+                            Byte[] outByteValues,
+                            Short[] outShortValues,
+                            Integer[] outIntValues,
+                            Long[] outLongValues,
+                            Float[] outFloatValues,
+                            Double[] outDoubleValues,
+                            @RegularEnum.EnumType Integer[] outEnumValues,
+                            @ExtensibleEnum.EnumType Integer[] outExtensibleEnumValues,
+                            Map<Integer, Boolean> outBoolMap,
+                            Map<Integer, Integer> outIntMap) -> {
+                        Assert.assertArrayEquals(boolValues, outBoolValues);
+                        Assert.assertArrayEquals(uByteValues, outUByteValues);
+                        Assert.assertArrayEquals(uShortValues, outUShortValues);
+                        Assert.assertArrayEquals(uIntValues, outUIntValues);
+                        Assert.assertArrayEquals(uLongValues, outULongValues);
+                        Assert.assertArrayEquals(byteValues, outByteValues);
+                        Assert.assertArrayEquals(shortValues, outShortValues);
+                        Assert.assertArrayEquals(intValues, outIntValues);
+                        Assert.assertArrayEquals(longValues, outLongValues);
+                        Assert.assertArrayEquals(floatValues, outFloatValues);
+                        Assert.assertArrayEquals(doubleValues, outDoubleValues);
+                        Assert.assertArrayEquals(enumValues, outEnumValues);
+                        Assert.assertArrayEquals(
+                                new Integer[] {
+                                    null, ExtensibleEnum.UNKNOWN, ExtensibleEnum.UNKNOWN
+                                },
+                                outExtensibleEnumValues);
+                        Assert.assertEquals(boolMap, outBoolMap);
+                        Assert.assertEquals(intMap, outIntMap);
+
+                        mTestRule.quitLoop();
+                    });
+            mTestRule.runLoopForever();
+        }
+
+        {
+            // Test bitfields that extend beyond the size of the element.
+            final int testSize = 1000;
+            Integer[] intValues = new Integer[testSize];
+            for (int i = 0; i < intValues.length; ++i) {
+                // Alternate between value and null.
+                intValues[i] = i % 2 == 0 ? i : null;
+            }
+
+            // Also test the case where we pass in empty containers.
+            remote.methodWithContainers(
+                    new Boolean[0],
+                    new Byte[0],
+                    new Short[0],
+                    intValues,
+                    new Long[0],
+                    new Byte[0],
+                    new Short[0],
+                    new Integer[0],
+                    new Long[0],
+                    new Float[0],
+                    new Double[0],
+                    new Integer[0],
+                    new Integer[0],
+                    new HashMap<Integer, Boolean>(),
+                    new HashMap<Integer, Integer>(),
+                    (Boolean[] outBoolValues,
+                            Byte[] outUByteValues,
+                            Short[] outUShortValues,
+                            Integer[] outUIntValues,
+                            Long[] outULongValues,
+                            Byte[] outByteValues,
+                            Short[] outShortValues,
+                            Integer[] outIntValues,
+                            Long[] outLongValues,
+                            Float[] outFloatValues,
+                            Double[] outDoubleValues,
+                            @RegularEnum.EnumType Integer[] outEnumValues,
+                            @ExtensibleEnum.EnumType Integer[] outExtensibleEnumValues,
+                            Map<Integer, Boolean> outBoolMap,
+                            Map<Integer, Integer> outIntMap) -> {
+                        Assert.assertArrayEquals(intValues, outUIntValues);
+
+                        mTestRule.quitLoop();
+                    });
+            mTestRule.runLoopForever();
+        }
+
+        {
+            remote.methodToSendUnknownEnum(
+                    (@ExtensibleEnum.EnumType Integer[] result) -> {
+                        Assert.assertArrayEquals(
+                                new Integer[] {ExtensibleEnum.UNKNOWN, null}, result);
+
+                        mTestRule.quitLoop();
+                    });
+
+            mTestRule.runLoopForever();
+        }
+
         // Versioned interfaces intentionally untested... for now.
     }
 
diff --git a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
index c967a69..05a65df 100644
--- a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
@@ -109,7 +109,12 @@
 {
 {%-     endif %}
     for (int i{{level+1}} = 0; i{{level+1}} < {{variable}}.length; ++i{{level+1}}) {
+{%-     if kind.kind|is_nullable_kind %}
+        if ({{variable}}[i{{level+1}}] == null)
+          continue;
+{%-     endif %}
         {{kind.kind|java_class_for_enum}}.validate({{variable}}[i{{level+1}}]);
+        {{variable}}[i{{level+1}}] = {{kind.kind|java_class_for_enum}}.toKnownValue({{variable}}[i{{level+1}}]);
     }
 }
 {%-   elif kind|is_enum_kind %}
diff --git a/mojo/public/tools/bindings/generators/mojom_java_generator.py b/mojo/public/tools/bindings/generators/mojom_java_generator.py
index 4c5a45a..ca1ca3ec 100644
--- a/mojo/public/tools/bindings/generators/mojom_java_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_java_generator.py
@@ -216,7 +216,9 @@
 def DecodeMethod(context, kind, offset, bit):
   def _DecodeMethodName(kind):
     if mojom.IsArrayKind(kind):
-      return _DecodeMethodName(kind.kind) + 's'
+      suffix = 'Nullables' if kind.kind.is_nullable and mojom.IsValueKind(
+          kind.kind) else 's'
+      return _DecodeMethodName(mojom.EnsureUnnullable(kind.kind)) + suffix
     if mojom.IsEnumKind(kind):
       return _DecodeMethodName(mojom.INT32)
     if mojom.IsPendingReceiverKind(kind):
diff --git a/native_client b/native_client
index 99f2552..0253008 160000
--- a/native_client
+++ b/native_client
@@ -1 +1 @@
-Subproject commit 99f2552944a668f9e7f61b8d8cae77b72d63a9b1
+Subproject commit 0253008929d3687acd30e9f618d9a14e88b81bf4
diff --git a/net/cookies/cookie_partition_key.cc b/net/cookies/cookie_partition_key.cc
index c17ebae..40007c4 100644
--- a/net/cookies/cookie_partition_key.cc
+++ b/net/cookies/cookie_partition_key.cc
@@ -138,13 +138,11 @@
     return std::nullopt;
   }
 
-  AncestorChainBit ancestor_chain_bit = [&]() -> AncestorChainBit {
-    if (nonce || site_for_cookies.IsNull()) {
-      return AncestorChainBit::kCrossSite;
-    }
-    return BoolToAncestorChainBit(
-        !site_for_cookies.IsFirstParty(request_site.GetURL()));
-  }();
+  const auto ancestor_chain_bit =
+      (nonce || site_for_cookies.IsNull())
+          ? AncestorChainBit::kCrossSite
+          : BoolToAncestorChainBit(
+                !site_for_cookies.IsFirstParty(request_site.GetURL()));
 
   return CookiePartitionKey(*partition_key_site, nonce, ancestor_chain_bit);
 }
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index b943fa7..e3808fe2 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -598,6 +598,8 @@
       "linux/wayland_seat.h",
       "x11_crtc_resizer.cc",
       "x11_crtc_resizer.h",
+      "x11_desktop_resizer.cc",
+      "x11_desktop_resizer.h",
       "x11_display_util.cc",
       "x11_display_util.h",
     ]
diff --git a/remoting/host/client_session.cc b/remoting/host/client_session.cc
index 4e1b1d6..e8b168ab 100644
--- a/remoting/host/client_session.cc
+++ b/remoting/host/client_session.cc
@@ -57,6 +57,7 @@
 #include "remoting/protocol/session.h"
 #include "remoting/protocol/session_config.h"
 #include "remoting/protocol/video_frame_pump.h"
+#include "remoting/protocol/webrtc_video_stream.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
 
 #if defined(WEBRTC_USE_GIO)
@@ -66,15 +67,10 @@
 namespace {
 
 constexpr char kRtcLogTransferDataChannelPrefix[] = "rtc-log-transfer-";
-constexpr char kStreamName[] = "screen_stream";
 
 constexpr base::TimeDelta kDefaultBoostCaptureInterval = base::Milliseconds(5);
 constexpr base::TimeDelta kDefaultBoostDuration = base::Milliseconds(50);
 
-std::string StreamNameForId(webrtc::ScreenId id) {
-  return std::string(kStreamName) + "_" + base::NumberToString(id);
-}
-
 }  // namespace
 
 namespace remoting {
@@ -585,7 +581,7 @@
   DCHECK(video_streams_.empty());
 
   auto video_stream = connection_->StartVideoStream(
-      kStreamName,
+      webrtc::kFullDesktopScreenId,
       desktop_environment_->CreateVideoCapturer(webrtc::kFullDesktopScreenId));
 
   // Create an AudioStream to pump audio from the capturer to the client.
@@ -627,14 +623,10 @@
       continue;
     }
 
-    auto stream_name = StreamNameForId(id);
-
-    HOST_LOG << "Creating video stream: " << stream_name;
+    HOST_LOG << "Creating video stream for id " << id;
 
     auto video_stream = connection_->StartVideoStream(
-        stream_name, desktop_environment_->CreateVideoCapturer(id));
-    // This call is needed by WebrtcVideoStream for per-frame stats reporting.
-    video_stream->SelectSource(id);
+        id, desktop_environment_->CreateVideoCapturer(id));
 
     // SetObserver(this) is not called on the new video-stream, because
     // per-monitor resizing should be handled by OnDesktopDisplayChanged()
@@ -1164,7 +1156,8 @@
     video_track = layout.add_video_track();
     video_track->CopyFrom(display);
     if (multiStreamEnabled) {
-      video_track->set_media_stream_id(StreamNameForId(display.screen_id()));
+      video_track->set_media_stream_id(
+          protocol::WebrtcVideoStream::StreamNameForId(display.screen_id()));
     }
 
     LOG(INFO) << "  Display " << display_id << " = " << display.position_x()
diff --git a/remoting/host/desktop_resizer_x11.cc b/remoting/host/desktop_resizer_x11.cc
index d5a8b32..393cbb38 100644
--- a/remoting/host/desktop_resizer_x11.cc
+++ b/remoting/host/desktop_resizer_x11.cc
@@ -4,649 +4,31 @@
 
 #include "remoting/host/desktop_resizer_x11.h"
 
-#include <gio/gio.h>
-
-#include <algorithm>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/command_line.h"
-#include "base/containers/contains.h"
-#include "base/memory/ptr_util.h"
-#include "base/notreached.h"
-#include "base/ranges/algorithm.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/system/sys_info.h"
-#include "base/types/cxx23_to_underlying.h"
-#include "remoting/base/logging.h"
-#include "remoting/host/desktop_display_layout_util.h"
-#include "remoting/host/desktop_geometry.h"
-#include "remoting/host/linux/x11_util.h"
-#include "remoting/host/x11_crtc_resizer.h"
-#include "remoting/host/x11_display_util.h"
-#include "remoting/proto/control.pb.h"
-#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
-#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/geometry/vector2d.h"
-#include "ui/gfx/x/future.h"
-#include "ui/gfx/x/randr.h"
-
-// On Linux, we use the xrandr extension to change the desktop resolution.
-//
-// Xrandr has a number of restrictions that make exact resize more complex:
-//
-//   1. It's not possible to change the resolution of an existing mode. Instead,
-//      the mode must be deleted and recreated.
-//   2. It's not possible to delete a mode that's in use.
-//   3. Errors are communicated via Xlib's spectacularly unhelpful mechanism
-//      of terminating the process unless you install an error handler.
-//   4. The root window size must always enclose any enabled Outputs (that is,
-//      any output which is attached to a CRTC).
-//   5. An Output cannot be given properties (xy-offsets, mode) which would
-//      extend its rectangle beyond the root window size.
-//
-// Since we want the current mode name to be consistent (for each Output), the
-// approach is as follows:
-//
-//   1. Fetch information about all the active (enabled) CRTCs.
-//   2. Disable the RANDR Output being resized.
-//   3. Delete the CRD mode, if it exists.
-//   4. Create the CRD mode at the new resolution, and add it to the Output's
-//      list of modes.
-//   5. Adjust the properties (in memory) of any CRTCs to be modified:
-//      * Width/height (mode) of the CRTC being resized.
-//      * xy-offsets to avoid overlapping CRTCs.
-//   6. Disable any CRTCs that might prevent changing the root window size.
-//   7. Compute the bounding rectangle of all CRTCs (after adjustment), and set
-//      it as the new root window size.
-//   8. Apply all adjusted CRTC properties to the CRTCs. This will set the
-//      Output being resized to the new CRD mode (which re-enables it), and it
-//      will re-enable any other CRTCs that were disabled.
-
-namespace {
-
-constexpr auto kInvalidMode = static_cast<x11::RandR::Mode>(0);
-constexpr auto kDisabledCrtc = static_cast<x11::RandR::Crtc>(0);
-constexpr base::TimeDelta kGnomeWaitTime = base::Seconds(1);
-
-int PixelsToMillimeters(int pixels, int dpi) {
-  DCHECK(dpi != 0);
-
-  const double kMillimetersPerInch = 25.4;
-
-  // (pixels / dpi) is the length in inches. Multiplying by
-  // kMillimetersPerInch converts to mm. Multiplication is done first to
-  // avoid integer division.
-  return static_cast<int>(kMillimetersPerInch * pixels / dpi);
-}
-
-// Returns a physical size in mm that will work well with GNOME's
-// automatic scale-selection algorithm.
-webrtc::DesktopSize CalculateSizeInMmForGnome(
-    const remoting::ScreenResolution& resolution) {
-  int width_mm = PixelsToMillimeters(resolution.dimensions().width(),
-                                     resolution.dpi().x());
-  int height_mm = PixelsToMillimeters(resolution.dimensions().height(),
-                                      resolution.dpi().y());
-
-  // GNOME will, by default, choose an automatic scaling-factor based on the
-  // monitor's physical size (mm) and resolution (pixels). Some versions of
-  // GNOME have a problem when the computed DPI is close to 192. GNOME
-  // calculates the DPI using:
-  // dpi = size_pixels / (size_mm / 25.4)
-  // This is the reverse of PixelsToMillimeters() which should result in
-  // the same values as resolution.dpi() except for any floating-point
-  // truncation errors. GNOME will choose 2x scaling only if both the width and
-  // height DPIs are strictly greater than 192. The problem is that a user might
-  // connect from a 192dpi device and then GNOME's choice of scaling is randomly
-  // subject to rounding errors. If the calculation worked out at exactly
-  // 192dpi, the inequality test would fail and GNOME would choose 1x scaling.
-  // To address this, width_mm/height_mm are decreased slightly (increasing the
-  // calculated DPI) to favor 2x over 1x scaling for 192dpi devices.
-  width_mm--;
-  height_mm--;
-
-  // GNOME treats some pairs of width/height values as untrustworthy and will
-  // always choose 1x scaling for them. These values come from
-  // meta_monitor_has_aspect_as_size() in
-  // https://gitlab.gnome.org/GNOME/mutter/-/blob/main/src/backends/meta-monitor-manager.c
-  constexpr std::pair<int, int> kBadSizes[] = {
-      {16, 9}, {16, 10}, {160, 90}, {160, 100}, {1600, 900}, {1600, 1000}};
-  if (base::Contains(kBadSizes, std::pair(width_mm, height_mm))) {
-    width_mm--;
-  }
-  return {width_mm, height_mm};
-}
-
-// TODO(jamiewalch): Use the correct DPI for the mode: http://crbug.com/172405.
-const int kDefaultDPI = 96;
-
-x11::RandR::Output GetOutputFromContext(void* context) {
-  return reinterpret_cast<x11::RandR::MonitorInfo*>(context)->outputs[0];
-}
-
-std::string GetModeNameForOutput(x11::RandR::Output output) {
-  // The name of the mode representing the current client view resolution. This
-  // must be unique per Output, so that Outputs can be resized independently.
-  return "CRD_" + base::NumberToString(base::to_underlying(output));
-}
-
-uint32_t GetDotClockForModeInfo() {
-  static int proc_num = base::SysInfo::NumberOfProcessors();
-  // Keep the proc_num logic in sync with linux_me2me_host.py
-  if (proc_num > 16) {
-    return 120 * 1e6;
-  }
-  return 60 * 1e6;
-}
-
-}  // namespace
-
 namespace remoting {
 
-ScreenResources::ScreenResources() = default;
-
-ScreenResources::~ScreenResources() = default;
-
-bool ScreenResources::Refresh(x11::RandR* randr, x11::Window window) {
-  resources_ = nullptr;
-  if (auto response = randr->GetScreenResourcesCurrent({window}).Sync()) {
-    resources_ = std::move(response.reply);
-  }
-  return resources_ != nullptr;
-}
-
-x11::RandR::Mode ScreenResources::GetIdForMode(const std::string& name) {
-  CHECK(resources_);
-  const char* names = reinterpret_cast<const char*>(resources_->names.data());
-  for (const auto& mode_info : resources_->modes) {
-    std::string mode_name(names, mode_info.name_len);
-    names += mode_info.name_len;
-    if (name == mode_name) {
-      return static_cast<x11::RandR::Mode>(mode_info.id);
-    }
-  }
-  return kInvalidMode;
-}
-
-x11::RandR::GetScreenResourcesCurrentReply* ScreenResources::get() {
-  return resources_.get();
-}
-
-DesktopResizerX11::DesktopResizerX11()
-    : connection_(x11::Connection::Get()),
-      randr_(&connection_->randr()),
-      screen_(&connection_->default_screen()),
-      root_(screen_->root),
-      is_virtual_session_(IsVirtualSession(connection_)) {
-  has_randr_ = randr_->present();
-  if (!has_randr_) {
-    return;
-  }
-  randr_->SelectInput({root_, x11::RandR::NotifyMask::ScreenChange});
-
-  gnome_display_config_.Init();
-  registry_ = TakeGObject(g_settings_new("org.gnome.desktop.interface"));
-}
-
+DesktopResizerX11::DesktopResizerX11() = default;
 DesktopResizerX11::~DesktopResizerX11() = default;
 
+// DesktopResizer interface
 ScreenResolution DesktopResizerX11::GetCurrentResolution(
     webrtc::ScreenId screen_id) {
-  // Process pending events so that the connection setup data is updated
-  // with the correct display metrics.
-  if (has_randr_) {
-    connection_->DispatchAll();
-  }
-
-  // RANDR does not allow fetching information on a particular monitor. So
-  // fetch all of them and try to find the requested monitor.
-  auto reply = randr_->GetMonitors({root_}).Sync();
-  if (reply) {
-    for (const auto& monitor : reply->monitors) {
-      if (static_cast<webrtc::ScreenId>(monitor.name) != screen_id) {
-        continue;
-      }
-      gfx::Vector2d dpi = GetMonitorDpi(monitor);
-      return ScreenResolution(
-          webrtc::DesktopSize(monitor.width, monitor.height),
-          webrtc::DesktopVector(dpi.x(), dpi.y()));
-    }
-  }
-
-  LOG(ERROR) << "Cannot find current resolution for screen ID " << screen_id
-             << ". Resolution of the default screen will be returned.";
-
-  ScreenResolution result(
-      webrtc::DesktopSize(connection_->default_screen().width_in_pixels,
-                          connection_->default_screen().height_in_pixels),
-      webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
-  return result;
+  return resizer_.GetCurrentResolution(screen_id);
 }
-
 std::list<ScreenResolution> DesktopResizerX11::GetSupportedResolutions(
     const ScreenResolution& preferred,
     webrtc::ScreenId screen_id) {
-  std::list<ScreenResolution> result;
-  if (!has_randr_ || !is_virtual_session_) {
-    return result;
-  }
-
-  // Clamp the specified size to something valid for the X server.
-  if (auto response = randr_->GetScreenSizeRange({root_}).Sync()) {
-    int width =
-        std::clamp(static_cast<uint16_t>(preferred.dimensions().width()),
-                   response->min_width, response->max_width);
-    int height =
-        std::clamp(static_cast<uint16_t>(preferred.dimensions().height()),
-                   response->min_height, response->max_height);
-    // Additionally impose a minimum size of 640x480, since anything smaller
-    // doesn't seem very useful.
-    result.emplace_back(
-        webrtc::DesktopSize(std::max(640, width), std::max(480, height)),
-        preferred.dpi());
-  }
-  return result;
+  return resizer_.GetSupportedResolutions(preferred, screen_id);
 }
-
 void DesktopResizerX11::SetResolution(const ScreenResolution& resolution,
                                       webrtc::ScreenId screen_id) {
-  if (!has_randr_ || !is_virtual_session_) {
-    return;
-  }
-
-  // Grab the X server while we're changing the display resolution. This ensures
-  // that the display configuration doesn't change under our feet.
-  ScopedXGrabServer grabber(connection_);
-
-  if (!resources_.Refresh(randr_, root_)) {
-    return;
-  }
-
-  // RANDR does not allow fetching information on a particular monitor. So
-  // fetch all of them and try to find the requested monitor.
-  auto reply = randr_->GetMonitors({root_}).Sync();
-  if (!reply) {
-    return;
-  }
-
-  for (const auto& monitor : reply->monitors) {
-    if (static_cast<webrtc::ScreenId>(monitor.name) != screen_id) {
-      continue;
-    }
-
-    if (monitor.outputs.size() != 1) {
-      // This implementation only supports resizing a Monitor attached to a
-      // single output. The case where size() > 1 should never occur with
-      // Xorg+video-dummy.
-      // TODO(crbug.com/1326339): Maybe support resizing a Monitor not
-      // attached to any Output?
-      LOG(ERROR) << "Monitor " << screen_id
-                 << " has unexpected #outputs: " << monitor.outputs.size();
-      return;
-    }
-
-    if (!monitor.automatic) {
-      // This implementation only supports resizing synthesized Monitors which
-      // automatically track their Outputs.
-      // TODO(crbug.com/1326339): Maybe support resizing manually-created
-      // Monitors?
-      LOG(ERROR) << "Not resizing Monitor " << screen_id
-                 << " that was created manually.";
-      return;
-    }
-
-    SetResolutionForOutput(monitor.outputs[0], resolution);
-    return;
-  }
-  LOG(ERROR) << "Monitor " << screen_id << " not found.";
+  resizer_.SetResolution(resolution, screen_id);
 }
-
 void DesktopResizerX11::RestoreResolution(const ScreenResolution& original,
                                           webrtc::ScreenId screen_id) {
-  SetResolution(original, screen_id);
+  resizer_.SetResolution(original, screen_id);
 }
-
 void DesktopResizerX11::SetVideoLayout(const protocol::VideoLayout& layout) {
-  if (!has_randr_ || !is_virtual_session_) {
-    return;
-  }
-
-  // Grab the X server while we're changing the display resolution. This ensures
-  // that the display configuration doesn't change under our feet.
-  ScopedXGrabServer grabber(connection_);
-
-  if (!resources_.Refresh(randr_, root_)) {
-    return;
-  }
-
-  auto reply = randr_->GetMonitors({root_}).Sync();
-  if (!reply) {
-    return;
-  }
-
-  std::vector<DesktopLayoutWithContext> current_displays;
-  for (auto& monitor : reply->monitors) {
-    // This implementation only supports resizing synthesized Monitors which
-    // automatically track their Outputs.
-    // TODO(crbug.com/1326339): Maybe support resizing manually-created
-    // monitors?
-    if (monitor.automatic) {
-      current_displays.push_back(
-          {.layout = ToVideoTrackLayout(monitor), .context = &monitor});
-    }
-  }
-
-  // Convert VideoLayout to DesktopLayoutSet.
-  DesktopLayoutSet desktop_layout;
-  for (const auto& video_track : layout.video_track()) {
-    desktop_layout.layouts.emplace_back(
-        video_track.screen_id(),
-        gfx::Rect(video_track.position_x(), video_track.position_y(),
-                  video_track.width(), video_track.height()),
-        gfx::Vector2d(video_track.x_dpi(), video_track.y_dpi()));
-  }
-  // TODO(yuweih): Verify that the layout is valid, e.g. no overlaps or gaps
-  // between displays.
-  DisplayLayoutDiff diff =
-      CalculateDisplayLayoutDiff(current_displays, desktop_layout);
-
-  X11CrtcResizer resizer(resources_.get(), connection_);
-  resizer.FetchActiveCrtcs();
-
-  // Add displays
-  const std::vector<DesktopLayout>& new_layouts = diff.new_displays.layouts;
-  if (!new_layouts.empty()) {
-    auto outputs = GetDisabledOutputs();
-    size_t i = 0u;
-    for (; i < outputs.size() && i < new_layouts.size(); i++) {
-      auto& output_pair = outputs[i];
-      auto output = output_pair.first;
-      auto& output_info = output_pair.second;
-      // For the video-dummy driver, the size of |crtcs| is exactly 1 and is
-      // different for each Output. In general, this is not true for other
-      // video-drivers, and the lists can overlap.
-      // TODO(yuweih): Consider making CRTC allocation smarter so it works with
-      // non-video-dummy drivers.
-      if (output_info.crtcs.empty()) {
-        LOG(ERROR) << "No available CRTC found associated with "
-                   << reinterpret_cast<char*>(output_info.name.data());
-        continue;
-      }
-      auto crtc = output_info.crtcs.front();
-      auto track_layout = new_layouts[i];
-      // Note that this has a weird behavior in GNOME, such that, if |output| is
-      // "disconnected", creating the mode somehow resizes all existing displays
-      // to 1024x768. Once the output is successfully enabled, it will remain
-      // "connected" and will no longer have the problem. The problem doesn't
-      // occur on XFCE or Cinnamon.
-      // TODO(yuweih): See if this is fixable, or at least implement some
-      // workaround, such as re-applying the layout.
-      auto mode =
-          UpdateMode(output, track_layout.width(), track_layout.height());
-      if (mode == kInvalidMode) {
-        LOG(ERROR) << "Failed to create new mode.";
-        continue;
-      }
-      resizer.AddActiveCrtc(
-          crtc, mode, {output},
-          gfx::Rect(track_layout.position_x(), track_layout.position_y(),
-                    track_layout.width(), track_layout.height()));
-      HOST_LOG << "Added display with crtc: " << base::to_underlying(crtc)
-               << ", output: " << base::to_underlying(output);
-    }
-    if (i < new_layouts.size()) {
-      LOG(WARNING) << "Failed to create " << (new_layouts.size() - i)
-                   << " display(s) due to insufficient resources.";
-    }
-  }
-
-  // Update displays
-  for (const auto& updated_display : diff.updated_displays) {
-    auto track_layout = updated_display.layout;
-    auto output = GetOutputFromContext(updated_display.context);
-    auto crtc = resizer.GetCrtcForOutput(output);
-    if (crtc == kDisabledCrtc) {
-      // This is not expected to happen. Disabled Outputs are not expected to
-      // have any Monitor, but |output| was found in the RRGetMonitors response,
-      // so it should have a CRTC attached.
-      LOG(ERROR) << "No CRTC found for output: " << base::to_underlying(output);
-      continue;
-    }
-    resizer.DisableCrtc(crtc);
-    auto mode = UpdateMode(output, track_layout.width(), track_layout.height());
-    if (mode == kInvalidMode) {
-      LOG(ERROR) << "Failed to create new mode.";
-      continue;
-    }
-    resizer.UpdateActiveCrtc(
-        crtc, mode,
-        gfx::Rect(track_layout.position_x(), track_layout.position_y(),
-                  track_layout.width(), track_layout.height()));
-    HOST_LOG << "Updated display with screen ID: "
-             << updated_display.layout.screen_id().value_or(-1);
-  }
-
-  // Remove displays
-  for (const auto& removed_display : diff.removed_displays) {
-    auto output = GetOutputFromContext(removed_display.context);
-    auto crtc = resizer.GetCrtcForOutput(output);
-    if (crtc == kDisabledCrtc) {
-      LOG(ERROR) << "No CRTC found for output: " << base::to_underlying(output);
-      continue;
-    }
-    resizer.DisableCrtc(crtc);
-    resizer.RemoveActiveCrtc(crtc);
-    DeleteMode(output, GetModeNameForOutput(output));
-    HOST_LOG << "Removed display with screen ID: "
-             << removed_display.layout.screen_id().value_or(-1);
-  }
-
-  resizer.NormalizeCrtcs();
-  UpdateRootWindow(resizer);
-}
-
-void DesktopResizerX11::SetResolutionForOutput(
-    x11::RandR::Output output,
-    const ScreenResolution& resolution) {
-  // Actually do the resize operation, preserving the current mode name. Note
-  // that we have to detach the output from the mode in order to delete the
-  // mode and re-create it with the new resolution. The output may also need to
-  // be detached from all modes in order to reduce the root window size.
-  HOST_LOG << "Resizing RANDR Output " << base::to_underlying(output) << " to "
-           << resolution.dimensions().width() << "x"
-           << resolution.dimensions().height();
-
-  X11CrtcResizer resizer(resources_.get(), connection_);
-
-  resizer.FetchActiveCrtcs();
-  auto crtc = resizer.GetCrtcForOutput(output);
-
-  if (crtc == kDisabledCrtc) {
-    // This is not expected to happen. Disabled Outputs are not expected to
-    // have any Monitor, but |output| was found in the RRGetMonitors response,
-    // so it should have a CRTC attached.
-    LOG(ERROR) << "No CRTC found for output: " << base::to_underlying(output);
-    return;
-  }
-
-  // Disable the output now, so that the old mode can be deleted and the new
-  // mode created and added to the output's available modes. The previous size
-  // and offsets will be stored in |resizer|.
-  resizer.DisableCrtc(crtc);
-
-  auto mode = UpdateMode(output, resolution.dimensions().width(),
-                         resolution.dimensions().height());
-  if (mode == kInvalidMode) {
-    // The CRTC is disabled, but there's no easy way to recover it here
-    // (the mode it was attached to has gone).
-    LOG(ERROR) << "Failed to create new mode.";
-    return;
-  }
-
-  // Update |active_crtcs_| with new sizes and offsets.
-  resizer.UpdateActiveCrtcs(crtc, mode,
-                            gfx::Size(resolution.dimensions().width(),
-                                      resolution.dimensions().height()));
-  UpdateRootWindow(resizer);
-
-  webrtc::DesktopSize size_mm = CalculateSizeInMmForGnome(resolution);
-  int width_mm = size_mm.width();
-  int height_mm = size_mm.height();
-  HOST_LOG << "Setting physical size in mm: " << width_mm << "x" << height_mm;
-  SetOutputPhysicalSizeInMM(connection_, output, width_mm, height_mm);
-
-  // Check to see if GNOME is using automatic-scaling. If the value is non-zero,
-  // the user prefers a particular scaling, so don't adjust the
-  // text-scaling-factor here.
-  if (g_settings_get_uint(registry_.get(), "scaling-factor") == 0U) {
-    // Start the timer to update the text-scaling-factor. Any previously
-    // started timer will be cancelled.
-    requested_dpi_ = resolution.dpi().x();
-    gnome_delay_timer_.Start(FROM_HERE, kGnomeWaitTime, this,
-                             &DesktopResizerX11::RequestGnomeDisplayConfig);
-  }
-}
-
-x11::RandR::Mode DesktopResizerX11::UpdateMode(x11::RandR::Output output,
-                                               int width,
-                                               int height) {
-  std::string mode_name = GetModeNameForOutput(output);
-  DeleteMode(output, mode_name);
-
-  // Set some clock values so that the computed refresh-rate is a realistic
-  // number:
-  // 60Hz = dot_clock / (htotal * vtotal).
-  // This allows GNOME's Display Settings tool to apply new settings for
-  // resolution/scaling - see crbug.com/1374488.
-  x11::RandR::ModeInfo mode;
-  mode.width = width;
-  mode.height = height;
-  mode.dot_clock = GetDotClockForModeInfo();
-  mode.htotal = 1000;
-  mode.vtotal = 1000;
-  mode.name_len = mode_name.size();
-  if (auto reply =
-          randr_->CreateMode({root_, mode, mode_name.c_str()}).Sync()) {
-    randr_->AddOutputMode({
-        output,
-        reply->mode,
-    });
-    return reply->mode;
-  }
-  return kInvalidMode;
-}
-
-void DesktopResizerX11::DeleteMode(x11::RandR::Output output,
-                                   const std::string& name) {
-  x11::RandR::Mode mode_id = resources_.GetIdForMode(name);
-  if (mode_id != kInvalidMode) {
-    randr_->DeleteOutputMode({output, mode_id});
-    randr_->DestroyMode({mode_id});
-    resources_.Refresh(randr_, root_);
-  }
-}
-
-void DesktopResizerX11::UpdateRootWindow(X11CrtcResizer& resizer) {
-  // Disable any CRTCs that have been changed, so that the root window can be
-  // safely resized to the bounding-box of the new CRTCs.
-  // This is non-optimal: the only CRTCs that need disabling are those whose
-  // original rectangles don't fit into the new root window - they are the ones
-  // that would prevent resizing the root window. But figuring these out would
-  // involve keeping track of all the original rectangles as well as the new
-  // ones. So, to keep the implementation simple (and working for any arbitrary
-  // layout algorithm), all changed CRTCs are disabled here.
-  resizer.DisableChangedCrtcs();
-
-  // Get the dimensions to resize the root window to.
-  auto dimensions = resizer.GetBoundingBox();
-
-  // TODO(lambroslambrou): Use the DPI from client size information.
-  uint32_t width_mm = PixelsToMillimeters(dimensions.width(), kDefaultDPI);
-  uint32_t height_mm = PixelsToMillimeters(dimensions.height(), kDefaultDPI);
-  randr_->SetScreenSize({root_, static_cast<uint16_t>(dimensions.width()),
-                         static_cast<uint16_t>(dimensions.height()), width_mm,
-                         height_mm});
-
-  resizer.MoveApplicationWindows();
-
-  // Apply the new CRTCs, which will re-enable any that were disabled.
-  resizer.ApplyActiveCrtcs();
-}
-
-DesktopResizerX11::OutputInfoList DesktopResizerX11::GetDisabledOutputs() {
-  OutputInfoList disabled_outputs;
-  for (x11::RandR::Output output : resources_.get()->outputs) {
-    auto reply = randr_
-                     ->GetOutputInfo({.output = output,
-                                      .config_timestamp =
-                                          resources_.get()->config_timestamp})
-                     .Sync();
-    if (!reply) {
-      continue;
-    }
-    if (reply->crtc == kDisabledCrtc) {
-      disabled_outputs.emplace_back(output, std::move(*reply.reply));
-    }
-  }
-  return disabled_outputs;
-}
-
-void DesktopResizerX11::RequestGnomeDisplayConfig() {
-  // Unretained() is safe because `this` owns gnome_display_config_ which
-  // cancels callbacks on destruction.
-  gnome_display_config_.GetMonitorsConfig(
-      base::BindOnce(&DesktopResizerX11::OnGnomeDisplayConfigReceived,
-                     base::Unretained(this)));
-}
-
-void DesktopResizerX11::OnGnomeDisplayConfigReceived(
-    GnomeDisplayConfig config) {
-  // Look for an enabled monitor. Disabled monitors have no Mode set - a
-  // monitor can become disabled by being added then removed (using the website
-  // Display options). The Xorg xf86-video-dummy driver has a quirk that, once a
-  // monitor becomes "connected", it stays forever in the connected state, even
-  // if it is later disabled. All connected monitors (enabled or disabled) are
-  // included in the GNOME config.
-
-  // For X11, the calculation of the text-scaling-factor does not depend on
-  // which enabled monitor is chosen here, because GNOME's X11 backend forces
-  // all monitors to have the same scale. However, it makes sense to select
-  // an enabled monitor, since a disabled monitor might not have a reliable
-  // "scale" property returned by GNOME.
-  auto monitor_iter =
-      base::ranges::find_if(config.monitors, [](const auto& entry) {
-        return entry.second.GetCurrentMode() != nullptr;
-      });
-  if (monitor_iter == base::ranges::end(config.monitors)) {
-    LOG(ERROR) << "No enabled monitor found in GNOME config.";
-    return;
-  }
-  const auto& monitor = monitor_iter->second;
-
-  if (monitor.scale == 0) {
-    // This should never happen - avoid division by 0.
-    return;
-  }
-
-  // The GNOME scaling, multiplied by the GNOME text-scaling-factor, will be the
-  // rendered scaling of text. This should be the client's requested DPI divided
-  // by kDefaultDPI.
-  double text_scaling_factor =
-      static_cast<double>(requested_dpi_) / kDefaultDPI / monitor.scale;
-  HOST_LOG << "Target DPI = " << requested_dpi_
-           << ", GNOME scale = " << monitor.scale
-           << ", calculated text-scaling = " << text_scaling_factor;
-
-  if (!g_settings_set_double(registry_.get(), "text-scaling-factor",
-                             text_scaling_factor)) {
-    // Just log a warning - failure is expected if the value falls outside the
-    // interval [0.5, 3.0].
-    LOG(WARNING) << "Failed to set text-scaling-factor.";
-  }
+  resizer_.SetVideoLayout(layout);
 }
 
 }  // namespace remoting
diff --git a/remoting/host/desktop_resizer_x11.h b/remoting/host/desktop_resizer_x11.h
index 00d46a6..c8e426c 100644
--- a/remoting/host/desktop_resizer_x11.h
+++ b/remoting/host/desktop_resizer_x11.h
@@ -5,41 +5,11 @@
 #ifndef REMOTING_HOST_DESKTOP_RESIZER_X11_H_
 #define REMOTING_HOST_DESKTOP_RESIZER_X11_H_
 
-#include <string.h>
-
-#include <utility>
-
-#include "base/memory/ptr_util.h"
-#include "base/memory/raw_ptr.h"
-#include "base/timer/timer.h"
-#include "remoting/base/logging.h"
 #include "remoting/host/desktop_resizer.h"
-#include "remoting/host/linux/gnome_display_config_dbus_client.h"
-#include "remoting/host/linux/scoped_glib.h"
-#include "remoting/host/linux/x11_util.h"
-#include "ui/gfx/x/connection.h"
-#include "ui/gfx/x/randr.h"
+#include "remoting/host/x11_desktop_resizer.h"
 
 namespace remoting {
 
-class X11CrtcResizer;
-
-// Wrapper class for the XRRScreenResources struct.
-class ScreenResources {
- public:
-  ScreenResources();
-  ~ScreenResources();
-
-  bool Refresh(x11::RandR* randr, x11::Window window);
-
-  x11::RandR::Mode GetIdForMode(const std::string& name);
-
-  x11::RandR::GetScreenResourcesCurrentReply* get();
-
- private:
-  std::unique_ptr<x11::RandR::GetScreenResourcesCurrentReply> resources_;
-};
-
 class DesktopResizerX11 : public DesktopResizer {
  public:
   DesktopResizerX11();
@@ -59,52 +29,7 @@
   void SetVideoLayout(const protocol::VideoLayout& layout) override;
 
  private:
-  using OutputInfoList = std::vector<
-      std::pair<x11::RandR::Output, x11::RandR::GetOutputInfoReply>>;
-
-  // Add a mode matching the specified resolution and switch to it.
-  void SetResolutionForOutput(x11::RandR::Output output,
-                              const ScreenResolution& resolution);
-
-  // Removes the existing mode from the output and replaces it with the new
-  // size. Returns the new mode ID, or None (0) on failure.
-  x11::RandR::Mode UpdateMode(x11::RandR::Output output, int width, int height);
-
-  // Remove the specified mode from the output, and delete it. If the mode is in
-  // use, it is not deleted.
-  // |name| should be set to GetModeNameForOutput(output). The parameter is to
-  // avoid creating the mode name twice.
-  void DeleteMode(x11::RandR::Output output, const std::string& name);
-
-  // Updates the root window using the bounding box of the CRTCs, then
-  // re-activate all CRTCs.
-  void UpdateRootWindow(X11CrtcResizer& resizer);
-
-  // Gets a list of outputs that are not connected to any CRTCs.
-  OutputInfoList GetDisabledOutputs();
-
-  void RequestGnomeDisplayConfig();
-  void OnGnomeDisplayConfigReceived(GnomeDisplayConfig config);
-
-  raw_ptr<x11::Connection> connection_;
-  const raw_ptr<x11::RandR> randr_ = nullptr;
-  const raw_ptr<const x11::Screen> screen_ = nullptr;
-  x11::Window root_;
-  ScreenResources resources_;
-  bool has_randr_;
-  bool is_virtual_session_;
-
-  // Used to fetch GNOME's current scale setting, so the correct
-  // text-scaling-factor can be calculated.
-  GnomeDisplayConfigDBusClient gnome_display_config_;
-
-  // Used to rate-limit requests to GNOME.
-  base::OneShotTimer gnome_delay_timer_;
-
-  int requested_dpi_;
-
-  // Used to set the text-scaling-factor.
-  ScopedGObject<GSettings> registry_;
+  X11DesktopResizer resizer_;
 };
 
 }  // namespace remoting
diff --git a/remoting/host/desktop_session_proxy.cc b/remoting/host/desktop_session_proxy.cc
index c8f91fb6..a0f6e663 100644
--- a/remoting/host/desktop_session_proxy.cc
+++ b/remoting/host/desktop_session_proxy.cc
@@ -360,15 +360,8 @@
     base::WeakPtr<IpcVideoFrameCapturer> capturer_weakptr) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (video_capturers_.size() > 1U) {
-    // SelectSource() should not be called in multi-stream mode, but
-    // WebrtcVideoStream currently calls it when setting the screen ID for
-    // stats-reporting.
-    // TODO: b/331679617 - Fix ClientSession/WebrtcVideoStream to not call
-    // SelectSource() in multi-stream mode.
-    LOG(WARNING) << "Ignoring SelectSource() for multi-stream.";
-    return;
-  }
+  // SelectSource() is not used in multi-stream mode.
+  DCHECK_LE(video_capturers_.size(), 1U);
 
   if (base::FindPtrOrNull(video_capturers_, new_id) == capturer_weakptr.get()) {
     // The capturer is already bound to `new_id`, so there's no value in
diff --git a/remoting/host/it2me_desktop_environment.cc b/remoting/host/it2me_desktop_environment.cc
index 8334c6d..6ff76e1 100644
--- a/remoting/host/it2me_desktop_environment.cc
+++ b/remoting/host/it2me_desktop_environment.cc
@@ -103,6 +103,11 @@
     capabilities += protocol::kFileTransferCapability;
   }
 
+  // TODO: joedow - Move MultiStream capability to a shared base
+  // class once all platforms and connection modes support it.
+  capabilities += " ";
+  capabilities += protocol::kMultiStreamCapability;
+
   return capabilities;
 }
 
diff --git a/remoting/host/x11_desktop_resizer.cc b/remoting/host/x11_desktop_resizer.cc
new file mode 100644
index 0000000..70004ed
--- /dev/null
+++ b/remoting/host/x11_desktop_resizer.cc
@@ -0,0 +1,652 @@
+// Copyright 2012 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/x11_desktop_resizer.h"
+
+#include <gio/gio.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/containers/contains.h"
+#include "base/memory/ptr_util.h"
+#include "base/notreached.h"
+#include "base/ranges/algorithm.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/system/sys_info.h"
+#include "base/types/cxx23_to_underlying.h"
+#include "remoting/base/logging.h"
+#include "remoting/host/desktop_display_layout_util.h"
+#include "remoting/host/desktop_geometry.h"
+#include "remoting/host/linux/x11_util.h"
+#include "remoting/host/x11_crtc_resizer.h"
+#include "remoting/host/x11_display_util.h"
+#include "remoting/proto/control.pb.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/vector2d.h"
+#include "ui/gfx/x/future.h"
+#include "ui/gfx/x/randr.h"
+
+// On Linux, we use the xrandr extension to change the desktop resolution.
+//
+// Xrandr has a number of restrictions that make exact resize more complex:
+//
+//   1. It's not possible to change the resolution of an existing mode. Instead,
+//      the mode must be deleted and recreated.
+//   2. It's not possible to delete a mode that's in use.
+//   3. Errors are communicated via Xlib's spectacularly unhelpful mechanism
+//      of terminating the process unless you install an error handler.
+//   4. The root window size must always enclose any enabled Outputs (that is,
+//      any output which is attached to a CRTC).
+//   5. An Output cannot be given properties (xy-offsets, mode) which would
+//      extend its rectangle beyond the root window size.
+//
+// Since we want the current mode name to be consistent (for each Output), the
+// approach is as follows:
+//
+//   1. Fetch information about all the active (enabled) CRTCs.
+//   2. Disable the RANDR Output being resized.
+//   3. Delete the CRD mode, if it exists.
+//   4. Create the CRD mode at the new resolution, and add it to the Output's
+//      list of modes.
+//   5. Adjust the properties (in memory) of any CRTCs to be modified:
+//      * Width/height (mode) of the CRTC being resized.
+//      * xy-offsets to avoid overlapping CRTCs.
+//   6. Disable any CRTCs that might prevent changing the root window size.
+//   7. Compute the bounding rectangle of all CRTCs (after adjustment), and set
+//      it as the new root window size.
+//   8. Apply all adjusted CRTC properties to the CRTCs. This will set the
+//      Output being resized to the new CRD mode (which re-enables it), and it
+//      will re-enable any other CRTCs that were disabled.
+
+namespace {
+
+constexpr auto kInvalidMode = static_cast<x11::RandR::Mode>(0);
+constexpr auto kDisabledCrtc = static_cast<x11::RandR::Crtc>(0);
+constexpr base::TimeDelta kGnomeWaitTime = base::Seconds(1);
+
+int PixelsToMillimeters(int pixels, int dpi) {
+  DCHECK(dpi != 0);
+
+  const double kMillimetersPerInch = 25.4;
+
+  // (pixels / dpi) is the length in inches. Multiplying by
+  // kMillimetersPerInch converts to mm. Multiplication is done first to
+  // avoid integer division.
+  return static_cast<int>(kMillimetersPerInch * pixels / dpi);
+}
+
+// Returns a physical size in mm that will work well with GNOME's
+// automatic scale-selection algorithm.
+webrtc::DesktopSize CalculateSizeInMmForGnome(
+    const remoting::ScreenResolution& resolution) {
+  int width_mm = PixelsToMillimeters(resolution.dimensions().width(),
+                                     resolution.dpi().x());
+  int height_mm = PixelsToMillimeters(resolution.dimensions().height(),
+                                      resolution.dpi().y());
+
+  // GNOME will, by default, choose an automatic scaling-factor based on the
+  // monitor's physical size (mm) and resolution (pixels). Some versions of
+  // GNOME have a problem when the computed DPI is close to 192. GNOME
+  // calculates the DPI using:
+  // dpi = size_pixels / (size_mm / 25.4)
+  // This is the reverse of PixelsToMillimeters() which should result in
+  // the same values as resolution.dpi() except for any floating-point
+  // truncation errors. GNOME will choose 2x scaling only if both the width and
+  // height DPIs are strictly greater than 192. The problem is that a user might
+  // connect from a 192dpi device and then GNOME's choice of scaling is randomly
+  // subject to rounding errors. If the calculation worked out at exactly
+  // 192dpi, the inequality test would fail and GNOME would choose 1x scaling.
+  // To address this, width_mm/height_mm are decreased slightly (increasing the
+  // calculated DPI) to favor 2x over 1x scaling for 192dpi devices.
+  width_mm--;
+  height_mm--;
+
+  // GNOME treats some pairs of width/height values as untrustworthy and will
+  // always choose 1x scaling for them. These values come from
+  // meta_monitor_has_aspect_as_size() in
+  // https://gitlab.gnome.org/GNOME/mutter/-/blob/main/src/backends/meta-monitor-manager.c
+  constexpr std::pair<int, int> kBadSizes[] = {
+      {16, 9}, {16, 10}, {160, 90}, {160, 100}, {1600, 900}, {1600, 1000}};
+  if (base::Contains(kBadSizes, std::pair(width_mm, height_mm))) {
+    width_mm--;
+  }
+  return {width_mm, height_mm};
+}
+
+// TODO(jamiewalch): Use the correct DPI for the mode: http://crbug.com/172405.
+const int kDefaultDPI = 96;
+
+x11::RandR::Output GetOutputFromContext(void* context) {
+  return reinterpret_cast<x11::RandR::MonitorInfo*>(context)->outputs[0];
+}
+
+std::string GetModeNameForOutput(x11::RandR::Output output) {
+  // The name of the mode representing the current client view resolution. This
+  // must be unique per Output, so that Outputs can be resized independently.
+  return "CRD_" + base::NumberToString(base::to_underlying(output));
+}
+
+uint32_t GetDotClockForModeInfo() {
+  static int proc_num = base::SysInfo::NumberOfProcessors();
+  // Keep the proc_num logic in sync with linux_me2me_host.py
+  if (proc_num > 16) {
+    return 120 * 1e6;
+  }
+  return 60 * 1e6;
+}
+
+}  // namespace
+
+namespace remoting {
+
+ScreenResources::ScreenResources() = default;
+
+ScreenResources::~ScreenResources() = default;
+
+bool ScreenResources::Refresh(x11::RandR* randr, x11::Window window) {
+  resources_ = nullptr;
+  if (auto response = randr->GetScreenResourcesCurrent({window}).Sync()) {
+    resources_ = std::move(response.reply);
+  }
+  return resources_ != nullptr;
+}
+
+x11::RandR::Mode ScreenResources::GetIdForMode(const std::string& name) {
+  CHECK(resources_);
+  const char* names = reinterpret_cast<const char*>(resources_->names.data());
+  for (const auto& mode_info : resources_->modes) {
+    std::string mode_name(names, mode_info.name_len);
+    names += mode_info.name_len;
+    if (name == mode_name) {
+      return static_cast<x11::RandR::Mode>(mode_info.id);
+    }
+  }
+  return kInvalidMode;
+}
+
+x11::RandR::GetScreenResourcesCurrentReply* ScreenResources::get() {
+  return resources_.get();
+}
+
+X11DesktopResizer::X11DesktopResizer()
+    : connection_(x11::Connection::Get()),
+      randr_(&connection_->randr()),
+      screen_(&connection_->default_screen()),
+      root_(screen_->root),
+      is_virtual_session_(IsVirtualSession(connection_)) {
+  has_randr_ = randr_->present();
+  if (!has_randr_) {
+    return;
+  }
+  randr_->SelectInput({root_, x11::RandR::NotifyMask::ScreenChange});
+
+  gnome_display_config_.Init();
+  registry_ = TakeGObject(g_settings_new("org.gnome.desktop.interface"));
+}
+
+X11DesktopResizer::~X11DesktopResizer() = default;
+
+ScreenResolution X11DesktopResizer::GetCurrentResolution(
+    webrtc::ScreenId screen_id) {
+  // Process pending events so that the connection setup data is updated
+  // with the correct display metrics.
+  if (has_randr_) {
+    connection_->DispatchAll();
+  }
+
+  // RANDR does not allow fetching information on a particular monitor. So
+  // fetch all of them and try to find the requested monitor.
+  auto reply = randr_->GetMonitors({root_}).Sync();
+  if (reply) {
+    for (const auto& monitor : reply->monitors) {
+      if (static_cast<webrtc::ScreenId>(monitor.name) != screen_id) {
+        continue;
+      }
+      gfx::Vector2d dpi = GetMonitorDpi(monitor);
+      return ScreenResolution(
+          webrtc::DesktopSize(monitor.width, monitor.height),
+          webrtc::DesktopVector(dpi.x(), dpi.y()));
+    }
+  }
+
+  LOG(ERROR) << "Cannot find current resolution for screen ID " << screen_id
+             << ". Resolution of the default screen will be returned.";
+
+  ScreenResolution result(
+      webrtc::DesktopSize(connection_->default_screen().width_in_pixels,
+                          connection_->default_screen().height_in_pixels),
+      webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
+  return result;
+}
+
+std::list<ScreenResolution> X11DesktopResizer::GetSupportedResolutions(
+    const ScreenResolution& preferred,
+    webrtc::ScreenId screen_id) {
+  std::list<ScreenResolution> result;
+  if (!has_randr_ || !is_virtual_session_) {
+    return result;
+  }
+
+  // Clamp the specified size to something valid for the X server.
+  if (auto response = randr_->GetScreenSizeRange({root_}).Sync()) {
+    int width =
+        std::clamp(static_cast<uint16_t>(preferred.dimensions().width()),
+                   response->min_width, response->max_width);
+    int height =
+        std::clamp(static_cast<uint16_t>(preferred.dimensions().height()),
+                   response->min_height, response->max_height);
+    // Additionally impose a minimum size of 640x480, since anything smaller
+    // doesn't seem very useful.
+    result.emplace_back(
+        webrtc::DesktopSize(std::max(640, width), std::max(480, height)),
+        preferred.dpi());
+  }
+  return result;
+}
+
+void X11DesktopResizer::SetResolution(const ScreenResolution& resolution,
+                                      webrtc::ScreenId screen_id) {
+  if (!has_randr_ || !is_virtual_session_) {
+    return;
+  }
+
+  // Grab the X server while we're changing the display resolution. This ensures
+  // that the display configuration doesn't change under our feet.
+  ScopedXGrabServer grabber(connection_);
+
+  if (!resources_.Refresh(randr_, root_)) {
+    return;
+  }
+
+  // RANDR does not allow fetching information on a particular monitor. So
+  // fetch all of them and try to find the requested monitor.
+  auto reply = randr_->GetMonitors({root_}).Sync();
+  if (!reply) {
+    return;
+  }
+
+  for (const auto& monitor : reply->monitors) {
+    if (static_cast<webrtc::ScreenId>(monitor.name) != screen_id) {
+      continue;
+    }
+
+    if (monitor.outputs.size() != 1) {
+      // This implementation only supports resizing a Monitor attached to a
+      // single output. The case where size() > 1 should never occur with
+      // Xorg+video-dummy.
+      // TODO(crbug.com/1326339): Maybe support resizing a Monitor not
+      // attached to any Output?
+      LOG(ERROR) << "Monitor " << screen_id
+                 << " has unexpected #outputs: " << monitor.outputs.size();
+      return;
+    }
+
+    if (!monitor.automatic) {
+      // This implementation only supports resizing synthesized Monitors which
+      // automatically track their Outputs.
+      // TODO(crbug.com/1326339): Maybe support resizing manually-created
+      // Monitors?
+      LOG(ERROR) << "Not resizing Monitor " << screen_id
+                 << " that was created manually.";
+      return;
+    }
+
+    SetResolutionForOutput(monitor.outputs[0], resolution);
+    return;
+  }
+  LOG(ERROR) << "Monitor " << screen_id << " not found.";
+}
+
+void X11DesktopResizer::RestoreResolution(const ScreenResolution& original,
+                                          webrtc::ScreenId screen_id) {
+  SetResolution(original, screen_id);
+}
+
+void X11DesktopResizer::SetVideoLayout(const protocol::VideoLayout& layout) {
+  if (!has_randr_ || !is_virtual_session_) {
+    return;
+  }
+
+  // Grab the X server while we're changing the display resolution. This ensures
+  // that the display configuration doesn't change under our feet.
+  ScopedXGrabServer grabber(connection_);
+
+  if (!resources_.Refresh(randr_, root_)) {
+    return;
+  }
+
+  auto reply = randr_->GetMonitors({root_}).Sync();
+  if (!reply) {
+    return;
+  }
+
+  std::vector<DesktopLayoutWithContext> current_displays;
+  for (auto& monitor : reply->monitors) {
+    // This implementation only supports resizing synthesized Monitors which
+    // automatically track their Outputs.
+    // TODO(crbug.com/1326339): Maybe support resizing manually-created
+    // monitors?
+    if (monitor.automatic) {
+      current_displays.push_back(
+          {.layout = ToVideoTrackLayout(monitor), .context = &monitor});
+    }
+  }
+
+  // Convert VideoLayout to DesktopLayoutSet.
+  DesktopLayoutSet desktop_layout;
+  for (const auto& video_track : layout.video_track()) {
+    desktop_layout.layouts.emplace_back(
+        video_track.screen_id(),
+        gfx::Rect(video_track.position_x(), video_track.position_y(),
+                  video_track.width(), video_track.height()),
+        gfx::Vector2d(video_track.x_dpi(), video_track.y_dpi()));
+  }
+  // TODO(yuweih): Verify that the layout is valid, e.g. no overlaps or gaps
+  // between displays.
+  DisplayLayoutDiff diff =
+      CalculateDisplayLayoutDiff(current_displays, desktop_layout);
+
+  X11CrtcResizer resizer(resources_.get(), connection_);
+  resizer.FetchActiveCrtcs();
+
+  // Add displays
+  const std::vector<DesktopLayout>& new_layouts = diff.new_displays.layouts;
+  if (!new_layouts.empty()) {
+    auto outputs = GetDisabledOutputs();
+    size_t i = 0u;
+    for (; i < outputs.size() && i < new_layouts.size(); i++) {
+      auto& output_pair = outputs[i];
+      auto output = output_pair.first;
+      auto& output_info = output_pair.second;
+      // For the video-dummy driver, the size of |crtcs| is exactly 1 and is
+      // different for each Output. In general, this is not true for other
+      // video-drivers, and the lists can overlap.
+      // TODO(yuweih): Consider making CRTC allocation smarter so it works with
+      // non-video-dummy drivers.
+      if (output_info.crtcs.empty()) {
+        LOG(ERROR) << "No available CRTC found associated with "
+                   << reinterpret_cast<char*>(output_info.name.data());
+        continue;
+      }
+      auto crtc = output_info.crtcs.front();
+      auto track_layout = new_layouts[i];
+      // Note that this has a weird behavior in GNOME, such that, if |output| is
+      // "disconnected", creating the mode somehow resizes all existing displays
+      // to 1024x768. Once the output is successfully enabled, it will remain
+      // "connected" and will no longer have the problem. The problem doesn't
+      // occur on XFCE or Cinnamon.
+      // TODO(yuweih): See if this is fixable, or at least implement some
+      // workaround, such as re-applying the layout.
+      auto mode =
+          UpdateMode(output, track_layout.width(), track_layout.height());
+      if (mode == kInvalidMode) {
+        LOG(ERROR) << "Failed to create new mode.";
+        continue;
+      }
+      resizer.AddActiveCrtc(
+          crtc, mode, {output},
+          gfx::Rect(track_layout.position_x(), track_layout.position_y(),
+                    track_layout.width(), track_layout.height()));
+      HOST_LOG << "Added display with crtc: " << base::to_underlying(crtc)
+               << ", output: " << base::to_underlying(output);
+    }
+    if (i < new_layouts.size()) {
+      LOG(WARNING) << "Failed to create " << (new_layouts.size() - i)
+                   << " display(s) due to insufficient resources.";
+    }
+  }
+
+  // Update displays
+  for (const auto& updated_display : diff.updated_displays) {
+    auto track_layout = updated_display.layout;
+    auto output = GetOutputFromContext(updated_display.context);
+    auto crtc = resizer.GetCrtcForOutput(output);
+    if (crtc == kDisabledCrtc) {
+      // This is not expected to happen. Disabled Outputs are not expected to
+      // have any Monitor, but |output| was found in the RRGetMonitors response,
+      // so it should have a CRTC attached.
+      LOG(ERROR) << "No CRTC found for output: " << base::to_underlying(output);
+      continue;
+    }
+    resizer.DisableCrtc(crtc);
+    auto mode = UpdateMode(output, track_layout.width(), track_layout.height());
+    if (mode == kInvalidMode) {
+      LOG(ERROR) << "Failed to create new mode.";
+      continue;
+    }
+    resizer.UpdateActiveCrtc(
+        crtc, mode,
+        gfx::Rect(track_layout.position_x(), track_layout.position_y(),
+                  track_layout.width(), track_layout.height()));
+    HOST_LOG << "Updated display with screen ID: "
+             << updated_display.layout.screen_id().value_or(-1);
+  }
+
+  // Remove displays
+  for (const auto& removed_display : diff.removed_displays) {
+    auto output = GetOutputFromContext(removed_display.context);
+    auto crtc = resizer.GetCrtcForOutput(output);
+    if (crtc == kDisabledCrtc) {
+      LOG(ERROR) << "No CRTC found for output: " << base::to_underlying(output);
+      continue;
+    }
+    resizer.DisableCrtc(crtc);
+    resizer.RemoveActiveCrtc(crtc);
+    DeleteMode(output, GetModeNameForOutput(output));
+    HOST_LOG << "Removed display with screen ID: "
+             << removed_display.layout.screen_id().value_or(-1);
+  }
+
+  resizer.NormalizeCrtcs();
+  UpdateRootWindow(resizer);
+}
+
+void X11DesktopResizer::SetResolutionForOutput(
+    x11::RandR::Output output,
+    const ScreenResolution& resolution) {
+  // Actually do the resize operation, preserving the current mode name. Note
+  // that we have to detach the output from the mode in order to delete the
+  // mode and re-create it with the new resolution. The output may also need to
+  // be detached from all modes in order to reduce the root window size.
+  HOST_LOG << "Resizing RANDR Output " << base::to_underlying(output) << " to "
+           << resolution.dimensions().width() << "x"
+           << resolution.dimensions().height();
+
+  X11CrtcResizer resizer(resources_.get(), connection_);
+
+  resizer.FetchActiveCrtcs();
+  auto crtc = resizer.GetCrtcForOutput(output);
+
+  if (crtc == kDisabledCrtc) {
+    // This is not expected to happen. Disabled Outputs are not expected to
+    // have any Monitor, but |output| was found in the RRGetMonitors response,
+    // so it should have a CRTC attached.
+    LOG(ERROR) << "No CRTC found for output: " << base::to_underlying(output);
+    return;
+  }
+
+  // Disable the output now, so that the old mode can be deleted and the new
+  // mode created and added to the output's available modes. The previous size
+  // and offsets will be stored in |resizer|.
+  resizer.DisableCrtc(crtc);
+
+  auto mode = UpdateMode(output, resolution.dimensions().width(),
+                         resolution.dimensions().height());
+  if (mode == kInvalidMode) {
+    // The CRTC is disabled, but there's no easy way to recover it here
+    // (the mode it was attached to has gone).
+    LOG(ERROR) << "Failed to create new mode.";
+    return;
+  }
+
+  // Update |active_crtcs_| with new sizes and offsets.
+  resizer.UpdateActiveCrtcs(crtc, mode,
+                            gfx::Size(resolution.dimensions().width(),
+                                      resolution.dimensions().height()));
+  UpdateRootWindow(resizer);
+
+  webrtc::DesktopSize size_mm = CalculateSizeInMmForGnome(resolution);
+  int width_mm = size_mm.width();
+  int height_mm = size_mm.height();
+  HOST_LOG << "Setting physical size in mm: " << width_mm << "x" << height_mm;
+  SetOutputPhysicalSizeInMM(connection_, output, width_mm, height_mm);
+
+  // Check to see if GNOME is using automatic-scaling. If the value is non-zero,
+  // the user prefers a particular scaling, so don't adjust the
+  // text-scaling-factor here.
+  if (g_settings_get_uint(registry_.get(), "scaling-factor") == 0U) {
+    // Start the timer to update the text-scaling-factor. Any previously
+    // started timer will be cancelled.
+    requested_dpi_ = resolution.dpi().x();
+    gnome_delay_timer_.Start(FROM_HERE, kGnomeWaitTime, this,
+                             &X11DesktopResizer::RequestGnomeDisplayConfig);
+  }
+}
+
+x11::RandR::Mode X11DesktopResizer::UpdateMode(x11::RandR::Output output,
+                                               int width,
+                                               int height) {
+  std::string mode_name = GetModeNameForOutput(output);
+  DeleteMode(output, mode_name);
+
+  // Set some clock values so that the computed refresh-rate is a realistic
+  // number:
+  // 60Hz = dot_clock / (htotal * vtotal).
+  // This allows GNOME's Display Settings tool to apply new settings for
+  // resolution/scaling - see crbug.com/1374488.
+  x11::RandR::ModeInfo mode;
+  mode.width = width;
+  mode.height = height;
+  mode.dot_clock = GetDotClockForModeInfo();
+  mode.htotal = 1000;
+  mode.vtotal = 1000;
+  mode.name_len = mode_name.size();
+  if (auto reply =
+          randr_->CreateMode({root_, mode, mode_name.c_str()}).Sync()) {
+    randr_->AddOutputMode({
+        output,
+        reply->mode,
+    });
+    return reply->mode;
+  }
+  return kInvalidMode;
+}
+
+void X11DesktopResizer::DeleteMode(x11::RandR::Output output,
+                                   const std::string& name) {
+  x11::RandR::Mode mode_id = resources_.GetIdForMode(name);
+  if (mode_id != kInvalidMode) {
+    randr_->DeleteOutputMode({output, mode_id});
+    randr_->DestroyMode({mode_id});
+    resources_.Refresh(randr_, root_);
+  }
+}
+
+void X11DesktopResizer::UpdateRootWindow(X11CrtcResizer& resizer) {
+  // Disable any CRTCs that have been changed, so that the root window can be
+  // safely resized to the bounding-box of the new CRTCs.
+  // This is non-optimal: the only CRTCs that need disabling are those whose
+  // original rectangles don't fit into the new root window - they are the ones
+  // that would prevent resizing the root window. But figuring these out would
+  // involve keeping track of all the original rectangles as well as the new
+  // ones. So, to keep the implementation simple (and working for any arbitrary
+  // layout algorithm), all changed CRTCs are disabled here.
+  resizer.DisableChangedCrtcs();
+
+  // Get the dimensions to resize the root window to.
+  auto dimensions = resizer.GetBoundingBox();
+
+  // TODO(lambroslambrou): Use the DPI from client size information.
+  uint32_t width_mm = PixelsToMillimeters(dimensions.width(), kDefaultDPI);
+  uint32_t height_mm = PixelsToMillimeters(dimensions.height(), kDefaultDPI);
+  randr_->SetScreenSize({root_, static_cast<uint16_t>(dimensions.width()),
+                         static_cast<uint16_t>(dimensions.height()), width_mm,
+                         height_mm});
+
+  resizer.MoveApplicationWindows();
+
+  // Apply the new CRTCs, which will re-enable any that were disabled.
+  resizer.ApplyActiveCrtcs();
+}
+
+X11DesktopResizer::OutputInfoList X11DesktopResizer::GetDisabledOutputs() {
+  OutputInfoList disabled_outputs;
+  for (x11::RandR::Output output : resources_.get()->outputs) {
+    auto reply = randr_
+                     ->GetOutputInfo({.output = output,
+                                      .config_timestamp =
+                                          resources_.get()->config_timestamp})
+                     .Sync();
+    if (!reply) {
+      continue;
+    }
+    if (reply->crtc == kDisabledCrtc) {
+      disabled_outputs.emplace_back(output, std::move(*reply.reply));
+    }
+  }
+  return disabled_outputs;
+}
+
+void X11DesktopResizer::RequestGnomeDisplayConfig() {
+  // Unretained() is safe because `this` owns gnome_display_config_ which
+  // cancels callbacks on destruction.
+  gnome_display_config_.GetMonitorsConfig(
+      base::BindOnce(&X11DesktopResizer::OnGnomeDisplayConfigReceived,
+                     base::Unretained(this)));
+}
+
+void X11DesktopResizer::OnGnomeDisplayConfigReceived(
+    GnomeDisplayConfig config) {
+  // Look for an enabled monitor. Disabled monitors have no Mode set - a
+  // monitor can become disabled by being added then removed (using the website
+  // Display options). The Xorg xf86-video-dummy driver has a quirk that, once a
+  // monitor becomes "connected", it stays forever in the connected state, even
+  // if it is later disabled. All connected monitors (enabled or disabled) are
+  // included in the GNOME config.
+
+  // For X11, the calculation of the text-scaling-factor does not depend on
+  // which enabled monitor is chosen here, because GNOME's X11 backend forces
+  // all monitors to have the same scale. However, it makes sense to select
+  // an enabled monitor, since a disabled monitor might not have a reliable
+  // "scale" property returned by GNOME.
+  auto monitor_iter =
+      base::ranges::find_if(config.monitors, [](const auto& entry) {
+        return entry.second.GetCurrentMode() != nullptr;
+      });
+  if (monitor_iter == base::ranges::end(config.monitors)) {
+    LOG(ERROR) << "No enabled monitor found in GNOME config.";
+    return;
+  }
+  const auto& monitor = monitor_iter->second;
+
+  if (monitor.scale == 0) {
+    // This should never happen - avoid division by 0.
+    return;
+  }
+
+  // The GNOME scaling, multiplied by the GNOME text-scaling-factor, will be the
+  // rendered scaling of text. This should be the client's requested DPI divided
+  // by kDefaultDPI.
+  double text_scaling_factor =
+      static_cast<double>(requested_dpi_) / kDefaultDPI / monitor.scale;
+  HOST_LOG << "Target DPI = " << requested_dpi_
+           << ", GNOME scale = " << monitor.scale
+           << ", calculated text-scaling = " << text_scaling_factor;
+
+  if (!g_settings_set_double(registry_.get(), "text-scaling-factor",
+                             text_scaling_factor)) {
+    // Just log a warning - failure is expected if the value falls outside the
+    // interval [0.5, 3.0].
+    LOG(WARNING) << "Failed to set text-scaling-factor.";
+  }
+}
+
+}  // namespace remoting
diff --git a/remoting/host/x11_desktop_resizer.h b/remoting/host/x11_desktop_resizer.h
new file mode 100644
index 0000000..1db5694
--- /dev/null
+++ b/remoting/host/x11_desktop_resizer.h
@@ -0,0 +1,112 @@
+// 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.
+
+#ifndef REMOTING_HOST_X11_DESKTOP_RESIZER_H_
+#define REMOTING_HOST_X11_DESKTOP_RESIZER_H_
+
+#include <string.h>
+
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/memory/raw_ptr.h"
+#include "base/timer/timer.h"
+#include "remoting/base/logging.h"
+#include "remoting/host/desktop_resizer.h"
+#include "remoting/host/linux/gnome_display_config_dbus_client.h"
+#include "remoting/host/linux/scoped_glib.h"
+#include "remoting/host/linux/x11_util.h"
+#include "ui/gfx/x/connection.h"
+#include "ui/gfx/x/randr.h"
+
+namespace remoting {
+
+class X11CrtcResizer;
+
+// Wrapper class for the XRRScreenResources struct.
+class ScreenResources {
+ public:
+  ScreenResources();
+  ~ScreenResources();
+
+  bool Refresh(x11::RandR* randr, x11::Window window);
+
+  x11::RandR::Mode GetIdForMode(const std::string& name);
+
+  x11::RandR::GetScreenResourcesCurrentReply* get();
+
+ private:
+  std::unique_ptr<x11::RandR::GetScreenResourcesCurrentReply> resources_;
+};
+
+// TODO(btriebw): Split this into a different file.
+class X11DesktopResizer {
+ public:
+  X11DesktopResizer();
+  X11DesktopResizer(const X11DesktopResizer&) = delete;
+  X11DesktopResizer& operator=(const X11DesktopResizer&) = delete;
+  ~X11DesktopResizer();
+
+  ScreenResolution GetCurrentResolution(webrtc::ScreenId screen_id);
+  std::list<ScreenResolution> GetSupportedResolutions(
+      const ScreenResolution& preferred,
+      webrtc::ScreenId screen_id);
+  void SetResolution(const ScreenResolution& resolution,
+                     webrtc::ScreenId screen_id);
+  void RestoreResolution(const ScreenResolution& original,
+                         webrtc::ScreenId screen_id);
+  void SetVideoLayout(const protocol::VideoLayout& layout);
+
+ private:
+  using OutputInfoList = std::vector<
+      std::pair<x11::RandR::Output, x11::RandR::GetOutputInfoReply>>;
+
+  // Add a mode matching the specified resolution and switch to it.
+  void SetResolutionForOutput(x11::RandR::Output output,
+                              const ScreenResolution& resolution);
+
+  // Removes the existing mode from the output and replaces it with the new
+  // size. Returns the new mode ID, or None (0) on failure.
+  x11::RandR::Mode UpdateMode(x11::RandR::Output output, int width, int height);
+
+  // Remove the specified mode from the output, and delete it. If the mode is
+  // in use, it is not deleted. |name| should be set to
+  // GetModeNameForOutput(output). The parameter is to avoid creating the mode
+  // name twice.
+  void DeleteMode(x11::RandR::Output output, const std::string& name);
+
+  // Updates the root window using the bounding box of the CRTCs, then
+  // re-activate all CRTCs.
+  void UpdateRootWindow(X11CrtcResizer& resizer);
+
+  // Gets a list of outputs that are not connected to any CRTCs.
+  OutputInfoList GetDisabledOutputs();
+
+  void RequestGnomeDisplayConfig();
+  void OnGnomeDisplayConfigReceived(GnomeDisplayConfig config);
+
+  raw_ptr<x11::Connection> connection_;
+  const raw_ptr<x11::RandR> randr_ = nullptr;
+  const raw_ptr<const x11::Screen> screen_ = nullptr;
+  x11::Window root_;
+  ScreenResources resources_;
+  bool has_randr_;
+  bool is_virtual_session_;
+
+  // Used to fetch GNOME's current scale setting, so the correct
+  // text-scaling-factor can be calculated.
+  GnomeDisplayConfigDBusClient gnome_display_config_;
+
+  // Used to rate-limit requests to GNOME.
+  base::OneShotTimer gnome_delay_timer_;
+
+  int requested_dpi_;
+
+  // Used to set the text-scaling-factor.
+  ScopedGObject<GSettings> registry_;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_X11_DESKTOP_RESIZER_H_
diff --git a/remoting/protocol/connection_to_client.h b/remoting/protocol/connection_to_client.h
index eb05b8e..b6673aa 100644
--- a/remoting/protocol/connection_to_client.h
+++ b/remoting/protocol/connection_to_client.h
@@ -12,6 +12,7 @@
 #include "remoting/base/session_options.h"
 #include "remoting/protocol/message_pipe.h"
 #include "remoting/protocol/transport.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
 
 namespace remoting {
 class DesktopCapturer;
@@ -85,9 +86,10 @@
   virtual void Disconnect(ErrorCode error) = 0;
 
   // Start video stream that sends screen content from |desktop_capturer| to the
-  // client.
+  // client. |screen_id| should be webrtc::kFullDesktopScreenId for
+  // single-stream mode, or the screen being captured for multi-stream mode.
   virtual std::unique_ptr<VideoStream> StartVideoStream(
-      const std::string& stream_name,
+      webrtc::ScreenId screen_id,
       std::unique_ptr<DesktopCapturer> desktop_capturer) = 0;
 
   // Starts an audio stream. Returns nullptr if audio is not supported by the
diff --git a/remoting/protocol/connection_unittest.cc b/remoting/protocol/connection_unittest.cc
index 5edb4b4d..5f13492 100644
--- a/remoting/protocol/connection_unittest.cc
+++ b/remoting/protocol/connection_unittest.cc
@@ -560,7 +560,7 @@
 
   std::unique_ptr<VideoStream> video_stream =
       host_connection_->StartVideoStream(
-          "screen_stream", std::make_unique<TestScreenCapturer>());
+          0, std::make_unique<TestScreenCapturer>());
 
   // Receive 5 frames.
   for (int i = 0; i < 5; ++i) {
@@ -585,7 +585,7 @@
 
   std::unique_ptr<VideoStream> video_stream =
       host_connection_->StartVideoStream(
-          "screen_stream", base::WrapUnique(new TestScreenCapturer()));
+          0, base::WrapUnique(new TestScreenCapturer()));
 
   WaitNextVideoFrame();
 }
@@ -636,7 +636,7 @@
 
   std::unique_ptr<VideoStream> video_stream =
       host_connection_->StartVideoStream(
-          "screen_stream", std::make_unique<TestScreenCapturer>());
+          0, std::make_unique<TestScreenCapturer>());
   video_stream->SetEventTimestampsSource(input_event_timestamps_source);
 
   WaitNextVideoFrame();
@@ -699,7 +699,7 @@
   auto capturer = std::make_unique<TestScreenCapturer>();
   capturer->FailNthFrame(0);
   auto video_stream =
-      host_connection_->StartVideoStream("screen_stream", std::move(capturer));
+      host_connection_->StartVideoStream(0, std::move(capturer));
 
   WaitNextVideoFrame();
 }
@@ -712,7 +712,7 @@
   auto capturer = std::make_unique<TestScreenCapturer>();
   capturer->FailNthFrame(1);
   auto video_stream =
-      host_connection_->StartVideoStream("screen_stream", std::move(capturer));
+      host_connection_->StartVideoStream(0, std::move(capturer));
 
   WaitNextVideoFrame();
   WaitNextVideoFrame();
diff --git a/remoting/protocol/fake_connection_to_client.cc b/remoting/protocol/fake_connection_to_client.cc
index 636e8273..4aad2196 100644
--- a/remoting/protocol/fake_connection_to_client.cc
+++ b/remoting/protocol/fake_connection_to_client.cc
@@ -59,7 +59,7 @@
 }
 
 std::unique_ptr<VideoStream> FakeConnectionToClient::StartVideoStream(
-    const std::string& stream_name,
+    webrtc::ScreenId screen_id,
     std::unique_ptr<DesktopCapturer> desktop_capturer) {
   desktop_capturer_ = std::move(desktop_capturer);
   if (video_stub_ && video_encode_task_runner_) {
diff --git a/remoting/protocol/fake_connection_to_client.h b/remoting/protocol/fake_connection_to_client.h
index 61af5337..aa6d7d08 100644
--- a/remoting/protocol/fake_connection_to_client.h
+++ b/remoting/protocol/fake_connection_to_client.h
@@ -67,7 +67,7 @@
   void SetEventHandler(EventHandler* event_handler) override;
 
   std::unique_ptr<VideoStream> StartVideoStream(
-      const std::string& stream_name,
+      webrtc::ScreenId screen_id,
       std::unique_ptr<DesktopCapturer> desktop_capturer) override;
   std::unique_ptr<AudioStream> StartAudioStream(
       std::unique_ptr<AudioSource> audio_source) override;
diff --git a/remoting/protocol/ice_connection_to_client.cc b/remoting/protocol/ice_connection_to_client.cc
index 7878a735..eebf32f8 100644
--- a/remoting/protocol/ice_connection_to_client.cc
+++ b/remoting/protocol/ice_connection_to_client.cc
@@ -93,7 +93,7 @@
 }
 
 std::unique_ptr<VideoStream> IceConnectionToClient::StartVideoStream(
-    const std::string& stream_name,
+    webrtc::ScreenId screen_id,
     std::unique_ptr<DesktopCapturer> desktop_capturer) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
diff --git a/remoting/protocol/ice_connection_to_client.h b/remoting/protocol/ice_connection_to_client.h
index 13a9b1c..3f8d3fe1 100644
--- a/remoting/protocol/ice_connection_to_client.h
+++ b/remoting/protocol/ice_connection_to_client.h
@@ -50,7 +50,7 @@
   Session* session() override;
   void Disconnect(ErrorCode error) override;
   std::unique_ptr<VideoStream> StartVideoStream(
-      const std::string& stream_name,
+      webrtc::ScreenId screen_id,
       std::unique_ptr<DesktopCapturer> desktop_capturer) override;
   std::unique_ptr<AudioStream> StartAudioStream(
       std::unique_ptr<AudioSource> audio_source) override;
diff --git a/remoting/protocol/webrtc_connection_to_client.cc b/remoting/protocol/webrtc_connection_to_client.cc
index c99fef69..c4aaf463 100644
--- a/remoting/protocol/webrtc_connection_to_client.cc
+++ b/remoting/protocol/webrtc_connection_to_client.cc
@@ -87,15 +87,14 @@
 }
 
 std::unique_ptr<VideoStream> WebrtcConnectionToClient::StartVideoStream(
-    const std::string& stream_name,
+    webrtc::ScreenId screen_id,
     std::unique_ptr<DesktopCapturer> desktop_capturer) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(transport_);
 
-  auto stream =
-      std::make_unique<WebrtcVideoStream>(stream_name, session_options_);
+  auto stream = std::make_unique<WebrtcVideoStream>(session_options_);
   stream->set_video_stats_dispatcher(video_stats_dispatcher_.GetWeakPtr());
-  stream->Start(std::move(desktop_capturer), transport_.get(),
+  stream->Start(screen_id, std::move(desktop_capturer), transport_.get(),
                 video_encoder_factory_);
   stream->SetEventTimestampsSource(
       event_dispatcher_->event_timestamps_source());
diff --git a/remoting/protocol/webrtc_connection_to_client.h b/remoting/protocol/webrtc_connection_to_client.h
index a095f108..b592abb 100644
--- a/remoting/protocol/webrtc_connection_to_client.h
+++ b/remoting/protocol/webrtc_connection_to_client.h
@@ -47,7 +47,7 @@
   Session* session() override;
   void Disconnect(ErrorCode error) override;
   std::unique_ptr<VideoStream> StartVideoStream(
-      const std::string& stream_name,
+      webrtc::ScreenId screen_id,
       std::unique_ptr<DesktopCapturer> desktop_capturer) override;
   std::unique_ptr<AudioStream> StartAudioStream(
       std::unique_ptr<AudioSource> audio_source) override;
diff --git a/remoting/protocol/webrtc_video_stream.cc b/remoting/protocol/webrtc_video_stream.cc
index 36c02a82..f9d9bf0 100644
--- a/remoting/protocol/webrtc_video_stream.cc
+++ b/remoting/protocol/webrtc_video_stream.cc
@@ -10,6 +10,7 @@
 #include "base/functional/bind.h"
 #include "base/location.h"
 #include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
@@ -65,7 +66,8 @@
 
 class WebrtcVideoStream::Core : public webrtc::DesktopCapturer::Callback {
  public:
-  Core(std::unique_ptr<DesktopCapturer> capturer,
+  Core(webrtc::ScreenId screen_id,
+       std::unique_ptr<DesktopCapturer> capturer,
        base::WeakPtr<WebrtcVideoStream> video_stream);
 
   Core(const Core&) = delete;
@@ -106,8 +108,9 @@
   // The current frame DPI.
   webrtc::DesktopVector frame_dpi_;
 
-  // Screen ID of the monitor being captured, from SelectSource().
-  webrtc::ScreenId screen_id_ = webrtc::kInvalidScreenId;
+  // Screen ID of the monitor being captured, from the initial value passed to
+  // WebrtcVideoStream::Start(), or from SelectSource().
+  webrtc::ScreenId screen_id_;
 
   // Stats of the frame that's being captured.
   std::unique_ptr<FrameStats> current_frame_stats_;
@@ -130,9 +133,11 @@
   THREAD_CHECKER(thread_checker_);
 };
 
-WebrtcVideoStream::Core::Core(std::unique_ptr<DesktopCapturer> capturer,
+WebrtcVideoStream::Core::Core(webrtc::ScreenId screen_id,
+                              std::unique_ptr<DesktopCapturer> capturer,
                               base::WeakPtr<WebrtcVideoStream> video_stream)
-    : capturer_(std::move(capturer)),
+    : screen_id_(screen_id),
+      capturer_(std::move(capturer)),
       video_stream_(std::move(video_stream)),
       video_stream_task_runner_(
           base::SingleThreadTaskRunner::GetCurrentDefault()) {
@@ -258,9 +263,8 @@
   capturer_->CaptureFrame();
 }
 
-WebrtcVideoStream::WebrtcVideoStream(const std::string& stream_name,
-                                     const SessionOptions& session_options)
-    : stream_name_(stream_name), session_options_(session_options) {
+WebrtcVideoStream::WebrtcVideoStream(const SessionOptions& session_options)
+    : session_options_(session_options) {
 // TODO(joedow): Dig into the threading model on other platforms to see if they
 // can also be updated to run on a dedicated thread.
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_ASH)
@@ -287,6 +291,7 @@
 }
 
 void WebrtcVideoStream::Start(
+    webrtc::ScreenId screen_id,
     std::unique_ptr<DesktopCapturer> desktop_capturer,
     WebrtcTransport* webrtc_transport,
     WebrtcVideoEncoderFactory* video_encoder_factory) {
@@ -301,15 +306,16 @@
   DCHECK(peer_connection_factory);
   DCHECK(peer_connection_);
 
+  std::string stream_name = StreamNameForId(screen_id);
   video_track_source_ = new rtc::RefCountedObject<WebrtcVideoTrackSource>(
       base::BindRepeating(&WebrtcVideoStream::OnSinkAddedOrUpdated,
                           weak_factory_.GetWeakPtr()));
   rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track =
       peer_connection_factory->CreateVideoTrack(video_track_source_,
-                                                stream_name_);
+                                                stream_name);
 
   webrtc::RtpTransceiverInit init;
-  init.stream_ids = {stream_name_};
+  init.stream_ids = {stream_name};
 
   // value() DCHECKs if AddTransceiver() fails, which only happens if a track
   // was already added with the stream label.
@@ -318,9 +324,9 @@
   webrtc_transport->OnVideoTransceiverCreated(transceiver_);
 
   video_encoder_factory->video_stream_event_router()
-      .SetVideoChannelStateObserver(stream_name_, weak_factory_.GetWeakPtr());
+      .SetVideoChannelStateObserver(stream_name, weak_factory_.GetWeakPtr());
 
-  core_ = std::make_unique<Core>(std::move(desktop_capturer),
+  core_ = std::make_unique<Core>(screen_id, std::move(desktop_capturer),
                                  weak_factory_.GetWeakPtr());
   core_task_runner_->PostTask(FROM_HERE,
                               base::BindOnce(&WebrtcVideoStream::Core::Start,
@@ -437,6 +443,16 @@
   DCHECK(result.ok()) << "SetParameters() failed: " << result.message();
 }
 
+// static
+std::string WebrtcVideoStream::StreamNameForId(webrtc::ScreenId id) {
+  if (id == webrtc::kFullDesktopScreenId) {
+    // Used in the single-stream case.
+    return "screen_stream";
+  }
+
+  return "screen_stream_" + base::NumberToString(id);
+}
+
 void WebrtcVideoStream::OnEncodedFrameSent(
     webrtc::EncodedImageCallback::Result result,
     const WebrtcVideoEncoder::EncodedFrame& frame) {
@@ -453,8 +469,8 @@
     return;
   }
 
-  // The down-cast is safe, because the |stats| object was originally created by
-  // this class and attached to the frame.
+  // The down-cast is safe, because the |stats| object was originally created
+  // by this class and attached to the frame.
   const auto* current_frame_stats =
       static_cast<const FrameStats*>(frame.stats.get());
   DCHECK(current_frame_stats);
@@ -518,10 +534,10 @@
   //   - A new max framerate is requested
   //   - WebRTC artificially lowers the framerate due to network conditions
   //
-  // We need to update the max_framerate for the stream in some of the scenarios
-  // but not for the others. In order to determine whether to update the
-  // RTPSender, we check the current max_framerate rather than the framerate in
-  // |wants|.
+  // We need to update the max_framerate for the stream in some of the
+  // scenarios but not for the others. In order to determine whether to update
+  // the RTPSender, we check the current max_framerate rather than the
+  // framerate in |wants|.
   auto sender = transceiver_->sender();
   if (sender) {
     for (auto& encoding : sender->GetParameters().encodings) {
diff --git a/remoting/protocol/webrtc_video_stream.h b/remoting/protocol/webrtc_video_stream.h
index 1e69f98..2ace917 100644
--- a/remoting/protocol/webrtc_video_stream.h
+++ b/remoting/protocol/webrtc_video_stream.h
@@ -41,8 +41,7 @@
 
 class WebrtcVideoStream : public VideoStream, public VideoChannelStateObserver {
  public:
-  WebrtcVideoStream(const std::string& stream_name,
-                    const SessionOptions& options);
+  explicit WebrtcVideoStream(const SessionOptions& options);
 
   WebrtcVideoStream(const WebrtcVideoStream&) = delete;
   WebrtcVideoStream& operator=(const WebrtcVideoStream&) = delete;
@@ -54,11 +53,13 @@
     video_stats_dispatcher_ = video_stats_dispatcher;
   }
 
-  void Start(std::unique_ptr<DesktopCapturer> desktop_capturer,
+  // |screen_id| should be kFullDesktopScreenId for single-stream mode, or
+  // the screen being captured for multi-stream mode.
+  void Start(webrtc::ScreenId screen_id,
+             std::unique_ptr<DesktopCapturer> desktop_capturer,
              WebrtcTransport* webrtc_transport,
              WebrtcVideoEncoderFactory* video_encoder_factory);
 
-  // VideoStream interface.
   void SetEventTimestampsSource(scoped_refptr<InputEventTimestampsSource>
                                     event_timestamps_source) override;
   void Pause(bool pause) override;
@@ -72,6 +73,11 @@
   void BoostFramerate(base::TimeDelta capture_interval,
                       base::TimeDelta boost_duration) override;
 
+  // Returns the stream name corresponding to the initial `screen_id` passed to
+  // Start(). Used for sending VideoLayout messages to the client, which
+  // include the stream-name for each display.
+  static std::string StreamNameForId(webrtc::ScreenId id);
+
   // VideoChannelStateObserver interface.
   void OnEncodedFrameSent(
       webrtc::EncodedImageCallback::Result result,
@@ -91,9 +97,6 @@
       std::unique_ptr<webrtc::DesktopFrame> desktop_frame,
       std::unique_ptr<WebrtcVideoEncoder::FrameStats> frame_stats);
 
-  // Label of the associated WebRTC video-stream.
-  std::string stream_name_;
-
   // Store the target framerate so we can set it on the RTP Sender when the SDP
   // is renegotiated (such as when the codec or codec profile is changed).
   int target_framerate_ = kTargetFrameRate;
diff --git a/services/network/network_service_memory_cache_unittest.cc b/services/network/network_service_memory_cache_unittest.cc
index 6285f72..bcfe598 100644
--- a/services/network/network_service_memory_cache_unittest.cc
+++ b/services/network/network_service_memory_cache_unittest.cc
@@ -821,6 +821,11 @@
 }
 
 TEST_F(NetworkServiceMemoryCacheTest, CanServe_DevToolsAttached) {
+  // TODO(crbug.com/328043119): Remove code associated with
+  // kAncestorChainBitEnabledInPartitionedCookies after it's enabled by default.
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      {net::features::kAncestorChainBitEnabledInPartitionedCookies}, {});
   ResourceRequest request = CreateRequest("/cacheable?max-age=120");
   request.devtools_request_id = "fake-id";
   StoreResponseToMemoryCache(request);
@@ -851,8 +856,10 @@
   }
   ASSERT_TRUE(has_expected_header);
 
-  EXPECT_EQ(net::CookiePartitionKey::FromURLForTesting(request.url),
-            devtools_observer.response_cookie_partition_key());
+  EXPECT_EQ(
+      net::CookiePartitionKey::FromURLForTesting(
+          request.url, net::CookiePartitionKey::AncestorChainBit::kCrossSite),
+      devtools_observer.response_cookie_partition_key());
 }
 
 TEST_F(NetworkServiceMemoryCacheTest, CanServe_ClientSecurityStateProvided) {
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
index bfa3da5f..640b655 100644
--- a/services/network/public/cpp/features.cc
+++ b/services/network/public/cpp/features.cc
@@ -422,7 +422,13 @@
 // even when the connection is using HTTP/1 for non-localhost requests.
 BASE_FEATURE(kCompressionDictionaryTransportOverHttp1,
              "CompressionDictionaryTransportOverHttp1",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
+// When this feature is enabled, Chromium can use stored shared dictionaries
+// even when the connection is using HTTP/2 for non-localhost requests.
+BASE_FEATURE(kCompressionDictionaryTransportOverHttp2,
+             "CompressionDictionaryTransportOverHttp2",
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // When this feature is enabled, Chromium will use stored shared dictionaries
 // only if the request URL is a localhost URL or the transport layer is using a
diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h
index a38917f..2253cda 100644
--- a/services/network/public/cpp/features.h
+++ b/services/network/public/cpp/features.h
@@ -162,6 +162,8 @@
 COMPONENT_EXPORT(NETWORK_CPP)
 BASE_DECLARE_FEATURE(kCompressionDictionaryTransportOverHttp1);
 COMPONENT_EXPORT(NETWORK_CPP)
+BASE_DECLARE_FEATURE(kCompressionDictionaryTransportOverHttp2);
+COMPONENT_EXPORT(NETWORK_CPP)
 BASE_DECLARE_FEATURE(kCompressionDictionaryTransportRequireKnownRootCert);
 
 // Enables visibility aware network service resource scheduler. When enabled,
diff --git a/services/network/shared_dictionary/shared_dictionary_network_transaction.cc b/services/network/shared_dictionary/shared_dictionary_network_transaction.cc
index 6ff0870..b7a7aa2 100644
--- a/services/network/shared_dictionary/shared_dictionary_network_transaction.cc
+++ b/services/network/shared_dictionary/shared_dictionary_network_transaction.cc
@@ -236,13 +236,20 @@
     return;
   }
 
-  if (!base::FeatureList::IsEnabled(
-          network::features::kCompressionDictionaryTransportOverHttp1) &&
-      negotiated_protocol_ != net::kProtoHTTP2 &&
-      negotiated_protocol_ != net::kProtoQUIC &&
-      !net::IsLocalhost(request_url)) {
-    shared_dictionary_.reset();
-    return;
+  if (!net::IsLocalhost(request_url)) {
+    if (!base::FeatureList::IsEnabled(
+            network::features::kCompressionDictionaryTransportOverHttp1) &&
+        negotiated_protocol_ != net::kProtoHTTP2 &&
+        negotiated_protocol_ != net::kProtoQUIC) {
+      shared_dictionary_.reset();
+      return;
+    }
+    if (!base::FeatureList::IsEnabled(
+            network::features::kCompressionDictionaryTransportOverHttp2) &&
+        negotiated_protocol_ == net::kProtoHTTP2) {
+      shared_dictionary_.reset();
+      return;
+    }
   }
   if (base::FeatureList::IsEnabled(
           network::features::
diff --git a/services/network/shared_dictionary/shared_dictionary_network_transaction_unittest.cc b/services/network/shared_dictionary/shared_dictionary_network_transaction_unittest.cc
index 32474b2..3397e63 100644
--- a/services/network/shared_dictionary/shared_dictionary_network_transaction_unittest.cc
+++ b/services/network/shared_dictionary/shared_dictionary_network_transaction_unittest.cc
@@ -1231,19 +1231,32 @@
   }
 }
 
-enum class ProtocolCheckFeatureTestCase {
+enum class ProtocolCheckHttp1TestCase {
   kAllowHttp1,
   kDoNotAllowHttp1,
 };
-std::string ToString(ProtocolCheckFeatureTestCase feature) {
+std::string ToString(ProtocolCheckHttp1TestCase feature) {
   switch (feature) {
-    case ProtocolCheckFeatureTestCase::kAllowHttp1:
+    case ProtocolCheckHttp1TestCase::kAllowHttp1:
       return "AllowHttp1";
-    case ProtocolCheckFeatureTestCase::kDoNotAllowHttp1:
+    case ProtocolCheckHttp1TestCase::kDoNotAllowHttp1:
       return "DoNotAllowHttp1";
   }
 }
 
+enum class ProtocolCheckHttp2TestCase {
+  kAllowHttp2,
+  kDoNotAllowHttp2,
+};
+std::string ToString(ProtocolCheckHttp2TestCase feature) {
+  switch (feature) {
+    case ProtocolCheckHttp2TestCase::kAllowHttp2:
+      return "AllowHttp2";
+    case ProtocolCheckHttp2TestCase::kDoNotAllowHttp2:
+      return "DoNotAllowHttp2";
+  }
+}
+
 enum class ProtocolCheckHostTestCase {
   kLocalHost,
   kNonLocalhost,
@@ -1260,7 +1273,8 @@
 class SharedDictionaryNetworkTransactionProtocolCheckTest
     : public SharedDictionaryNetworkTransactionTest,
       public testing::WithParamInterface<
-          std::tuple<ProtocolCheckFeatureTestCase,
+          std::tuple<ProtocolCheckHttp1TestCase,
+                     ProtocolCheckHttp2TestCase,
                      ProtocolCheckProtocolTestCase,
                      ProtocolCheckHostTestCase>> {
  public:
@@ -1274,6 +1288,13 @@
       disabled_features.push_back(
           network::features::kCompressionDictionaryTransportOverHttp1);
     }
+    if (AllowHttp2()) {
+      enabled_features.push_back(
+          network::features::kCompressionDictionaryTransportOverHttp2);
+    } else {
+      disabled_features.push_back(
+          network::features::kCompressionDictionaryTransportOverHttp2);
+    }
     scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
   SharedDictionaryNetworkTransactionProtocolCheckTest(
@@ -1306,19 +1327,37 @@
 
  private:
   bool AllowHttp1() const {
-    return std::get<0>(GetParam()) == ProtocolCheckFeatureTestCase::kAllowHttp1;
+    return std::get<0>(GetParam()) == ProtocolCheckHttp1TestCase::kAllowHttp1;
+  }
+  bool AllowHttp2() const {
+    return std::get<1>(GetParam()) == ProtocolCheckHttp2TestCase::kAllowHttp2;
+  }
+  bool IsHttp1() const {
+    return std::get<2>(GetParam()) == ProtocolCheckProtocolTestCase::kHttp1;
   }
   bool IsHttp2() const {
-    return std::get<1>(GetParam()) == ProtocolCheckProtocolTestCase::kHttp2;
+    return std::get<2>(GetParam()) == ProtocolCheckProtocolTestCase::kHttp2;
   }
   bool IsHttp3() const {
-    return std::get<1>(GetParam()) == ProtocolCheckProtocolTestCase::kHttp3;
+    return std::get<2>(GetParam()) == ProtocolCheckProtocolTestCase::kHttp3;
   }
   bool IsLocalHost() const {
-    return std::get<2>(GetParam()) == ProtocolCheckHostTestCase::kLocalHost;
+    return std::get<3>(GetParam()) == ProtocolCheckHostTestCase::kLocalHost;
   }
   bool ShuoldUseDictionary() const {
-    return AllowHttp1() || IsLocalHost() || IsHttp2() || IsHttp3();
+    if (AllowHttp1()) {
+      if (AllowHttp2()) {
+        return true;
+      } else {
+        return IsLocalHost() || IsHttp1() || IsHttp3();
+      }
+    } else {
+      if (AllowHttp2()) {
+        return IsLocalHost() || IsHttp2() || IsHttp3();
+      } else {
+        return IsLocalHost() || IsHttp3();
+      }
+    }
   }
 
   base::test::ScopedFeatureList scoped_feature_list_;
@@ -1328,20 +1367,24 @@
     All,
     SharedDictionaryNetworkTransactionProtocolCheckTest,
     ::testing::Combine(
-        ::testing::Values(ProtocolCheckFeatureTestCase::kAllowHttp1,
-                          ProtocolCheckFeatureTestCase::kDoNotAllowHttp1),
+        ::testing::Values(ProtocolCheckHttp1TestCase::kAllowHttp1,
+                          ProtocolCheckHttp1TestCase::kDoNotAllowHttp1),
+        ::testing::Values(ProtocolCheckHttp2TestCase::kAllowHttp2,
+                          ProtocolCheckHttp2TestCase::kDoNotAllowHttp2),
         ::testing::Values(ProtocolCheckProtocolTestCase::kHttp1,
                           ProtocolCheckProtocolTestCase::kHttp2,
                           ProtocolCheckProtocolTestCase::kHttp3),
         ::testing::Values(ProtocolCheckHostTestCase::kLocalHost,
                           ProtocolCheckHostTestCase::kNonLocalhost)),
-    [](const testing::TestParamInfo<std::tuple<ProtocolCheckFeatureTestCase,
+    [](const testing::TestParamInfo<std::tuple<ProtocolCheckHttp1TestCase,
+                                               ProtocolCheckHttp2TestCase,
                                                ProtocolCheckProtocolTestCase,
                                                ProtocolCheckHostTestCase>>&
            info) {
       return ToString(std::get<0>(info.param)) + "_" +
              ToString(std::get<1>(info.param)) + "_" +
-             ToString(std::get<2>(info.param));
+             ToString(std::get<2>(info.param)) + "_" +
+             ToString(std::get<3>(info.param));
     });
 
 TEST_P(SharedDictionaryNetworkTransactionProtocolCheckTest, Basic) {
@@ -1349,9 +1392,11 @@
       base::MakeRefCounted<DummySharedDictionaryStorage>(
           std::make_unique<DummySyncDictionary>(kTestDictionaryData)));
 
-  net::MockTransaction new_mock_transaction = CreateMockTransaction();
+  // Reset `scoped_mock_transaction_` to use the custom ScopedMockTransaction.
+  scoped_mock_transaction_.reset();
+  net::ScopedMockTransaction new_mock_transaction(CreateMockTransaction());
 
-  net::MockHttpRequest request(*scoped_mock_transaction_);
+  net::MockHttpRequest request(new_mock_transaction);
   SharedDictionaryNetworkTransaction transaction(manager,
                                                  CreateNetworkTransaction());
   transaction.SetIsSharedDictionaryReadAllowedCallback(
diff --git a/services/video_effects/BUILD.gn b/services/video_effects/BUILD.gn
index 0904a9a5..f90038b6 100644
--- a/services/video_effects/BUILD.gn
+++ b/services/video_effects/BUILD.gn
@@ -9,11 +9,13 @@
   public = [
     "video_effects_processor_impl.h",
     "video_effects_service_impl.h",
+    "viz_gpu_channel_host_provider.h",
   ]
 
   sources = [
     "video_effects_processor_impl.cc",
     "video_effects_service_impl.cc",
+    "viz_gpu_channel_host_provider.cc",
   ]
 
   visibility = [
@@ -39,12 +41,15 @@
   testonly = true
 
   sources = [
+    "test_gpu_channel_host_provider.cc",
+    "test_gpu_channel_host_provider.h",
     "video_effects_processor_impl_unittest.cc",
     "video_effects_service_impl_unittest.cc",
   ]
 
   deps = [
     "//base/test:test_support",
+    "//gpu/ipc/common:test_support",
     "//media/capture/mojom:video_effects_manager",
     "//services/video_effects:service",
     "//testing/gtest",
diff --git a/services/video_effects/DEPS b/services/video_effects/DEPS
index 8ecd326..1fc05b1f 100644
--- a/services/video_effects/DEPS
+++ b/services/video_effects/DEPS
@@ -1,4 +1,6 @@
 include_rules = [
+  "+components/viz/common/gpu",
+  "+content/public/common",
   "+gpu/command_buffer/common",
   "+gpu/ipc/client",
   "+media/base",
@@ -6,3 +8,13 @@
   "+services/viz/public/cpp/gpu",
   "+services/video_effects/public/mojom",
 ]
+
+specific_include_rules = {
+  "test_gpu_channel_host_provider\.[cc|h]": [
+    "+gpu/config",
+    "+gpu/ipc/common",
+  ],
+  ".*_unittest\.cc": [
+    "+gpu/ipc/common",
+  ],
+}
diff --git a/services/video_effects/public/mojom/video_effects_processor.mojom b/services/video_effects/public/mojom/video_effects_processor.mojom
index f9b4e93d..a51edafa 100644
--- a/services/video_effects/public/mojom/video_effects_processor.mojom
+++ b/services/video_effects/public/mojom/video_effects_processor.mojom
@@ -12,6 +12,16 @@
 // `VideoEffectsProcessor::PostProcess()` failed.
 enum PostProcessError {
   kUnknown = 1,
+  // Returned when a postprocessor is no longer usable for some reason.
+  // This is not a recoverable error and the postprocessor should only return
+  // this temporarily - postprocessor teardown should happen shortly and it
+  // will be disconnected.
+  kUnusable = 2,
+  // Returned when a postprocessor is not yet ready to accept the calls.
+  // This can happen e.g. when it was not yet initialized. This should be
+  // treated like a recoverable error (i.e. the processor should eventually
+  // recover from this state, or become disconnected).
+  kNotReady = 3,
 };
 
 // Returned as the active variant of `PostProcessResult` if the call to
diff --git a/services/video_effects/test_gpu_channel_host_provider.cc b/services/video_effects/test_gpu_channel_host_provider.cc
new file mode 100644
index 0000000..f12ba30
--- /dev/null
+++ b/services/video_effects/test_gpu_channel_host_provider.cc
@@ -0,0 +1,49 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/video_effects/test_gpu_channel_host_provider.h"
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/scoped_refptr.h"
+#include "gpu/command_buffer/common/shared_image_capabilities.h"
+#include "gpu/config/gpu_feature_info.h"
+#include "gpu/config/gpu_info.h"
+#include "gpu/ipc/client/gpu_channel_host.h"
+#include "gpu/ipc/common/gpu_channel.mojom.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace video_effects {
+
+class TestGpuChannelHost : public gpu::GpuChannelHost {
+ public:
+  explicit TestGpuChannelHost(gpu::mojom::GpuChannel& gpu_channel)
+      : GpuChannelHost(0 /* channel_id */,
+                       gpu::GPUInfo(),
+                       gpu::GpuFeatureInfo(),
+                       gpu::SharedImageCapabilities(),
+                       mojo::ScopedMessagePipeHandle(
+                           mojo::MessagePipeHandle(mojo::kInvalidHandleValue))),
+        gpu_channel_(gpu_channel) {}
+
+  gpu::mojom::GpuChannel& GetGpuChannel() override {
+    return gpu_channel_.get();
+  }
+
+ protected:
+  ~TestGpuChannelHost() override = default;
+
+ private:
+  const raw_ref<gpu::mojom::GpuChannel> gpu_channel_;
+};
+
+TestGpuChannelHostProvider::TestGpuChannelHostProvider(
+    gpu::mojom::GpuChannel& gpu_channel)
+    : gpu_channel_(gpu_channel) {}
+
+scoped_refptr<gpu::GpuChannelHost>
+TestGpuChannelHostProvider::GetGpuChannelHost() {
+  return base::MakeRefCounted<TestGpuChannelHost>(gpu_channel_.get());
+}
+
+}  // namespace video_effects
diff --git a/services/video_effects/test_gpu_channel_host_provider.h b/services/video_effects/test_gpu_channel_host_provider.h
new file mode 100644
index 0000000..b3f8ad2
--- /dev/null
+++ b/services/video_effects/test_gpu_channel_host_provider.h
@@ -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.
+
+#ifndef SERVICES_VIDEO_EFFECTS_TEST_GPU_CHANNEL_HOST_PROVIDER_H_
+#define SERVICES_VIDEO_EFFECTS_TEST_GPU_CHANNEL_HOST_PROVIDER_H_
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/scoped_refptr.h"
+#include "gpu/ipc/client/gpu_channel_host.h"
+#include "gpu/ipc/common/gpu_channel.mojom-forward.h"
+#include "services/video_effects/video_effects_service_impl.h"
+
+namespace video_effects {
+
+class TestGpuChannelHostProvider : public GpuChannelHostProvider {
+ public:
+  explicit TestGpuChannelHostProvider(gpu::mojom::GpuChannel& gpu_channel);
+
+  scoped_refptr<gpu::GpuChannelHost> GetGpuChannelHost() override;
+
+ private:
+  const raw_ref<gpu::mojom::GpuChannel> gpu_channel_;
+};
+
+}  // namespace video_effects
+
+#endif  // SERVICES_VIDEO_EFFECTS_TEST_GPU_CHANNEL_HOST_PROVIDER_H_
diff --git a/services/video_effects/video_effects_processor_impl.cc b/services/video_effects/video_effects_processor_impl.cc
index 11997c35..3700fb99 100644
--- a/services/video_effects/video_effects_processor_impl.cc
+++ b/services/video_effects/video_effects_processor_impl.cc
@@ -4,18 +4,197 @@
 
 #include "services/video_effects/video_effects_processor_impl.h"
 
+#include "base/functional/bind.h"
+#include "base/functional/callback_forward.h"
+#include "base/types/cxx23_to_underlying.h"
+#include "content/public/common/gpu_stream_constants.h"
+#include "gpu/ipc/client/client_shared_image_interface.h"
+#include "gpu/ipc/client/gpu_channel_host.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "services/video_effects/public/mojom/video_effects_processor.mojom.h"
+#include "services/video_effects/video_effects_service_impl.h"
+#include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
+
+namespace {
+
+// Maximum number of context losses that the postprocessor will tolerate before
+// entering unrecoverable state:
+constexpr int kMaxNumOfContextLosses = 5;
+
+bool ContextLossesExceedThreshold(int num_context_losses) {
+  return num_context_losses >= kMaxNumOfContextLosses;
+}
+
+scoped_refptr<viz::ContextProviderCommandBuffer> CreateAndBindContextProvider(
+    scoped_refptr<gpu::GpuChannelHost> gpu_channel_host,
+    gpu::ContextType context_type) {
+  CHECK(gpu_channel_host);
+  CHECK(!gpu_channel_host->IsLost());
+  CHECK(context_type == gpu::CONTEXT_TYPE_WEBGPU ||
+        context_type == gpu::CONTEXT_TYPE_OPENGLES2);
+
+  auto context_creation_attribs = gpu::ContextCreationAttribs();
+  context_creation_attribs.context_type = context_type;
+  context_creation_attribs.enable_gles2_interface = false;
+  context_creation_attribs.enable_raster_interface =
+      context_type == gpu::CONTEXT_TYPE_OPENGLES2;
+  context_creation_attribs.bind_generates_resource =
+      context_type == gpu::CONTEXT_TYPE_WEBGPU;
+
+  // TODO(bialpio): replace `gpu::SharedMemoryLimits::ForOOPRasterContext()`
+  // with something better suited or explain why it's appropriate the way it is
+  // now.
+  scoped_refptr<viz::ContextProviderCommandBuffer> context_provider =
+      base::MakeRefCounted<viz::ContextProviderCommandBuffer>(
+          std::move(gpu_channel_host), content::kGpuStreamIdDefault,
+          gpu::SchedulingPriority::kNormal, gpu::kNullSurfaceHandle,
+          GURL("chrome://gpu/VideoEffects"), true /* automatic flushes */,
+          false /* support locking */,
+          context_type == gpu::CONTEXT_TYPE_WEBGPU
+              ? gpu::SharedMemoryLimits::ForWebGPUContext()
+              : gpu::SharedMemoryLimits::ForOOPRasterContext(),
+          context_creation_attribs,
+          viz::command_buffer_metrics::ContextType::VIDEO_CAPTURE);
+
+  const gpu::ContextResult context_result =
+      context_provider->BindToCurrentSequence();
+  if (context_result != gpu::ContextResult::kSuccess) {
+    LOG(ERROR) << "Bind context provider failed. context_result: "
+               << base::to_underlying(context_result);
+    return nullptr;
+  }
+
+  return context_provider;
+}
+
+}  // namespace
 
 namespace video_effects {
 
 VideoEffectsProcessorImpl::VideoEffectsProcessorImpl(
     mojo::PendingRemote<media::mojom::VideoEffectsManager> manager_remote,
-    mojo::PendingReceiver<mojom::VideoEffectsProcessor> processor_receiver)
+    mojo::PendingReceiver<mojom::VideoEffectsProcessor> processor_receiver,
+    GpuChannelHostProvider& gpu_channel_host_provider,
+    base::OnceClosure on_unrecoverable_error)
     : manager_remote_(std::move(manager_remote)),
-      processor_receiver_(this, std::move(processor_receiver)) {}
+      processor_receiver_(this, std::move(processor_receiver)),
+      gpu_channel_host_provider_(gpu_channel_host_provider),
+      on_unrecoverable_error_(std::move(on_unrecoverable_error)) {
+  processor_receiver_.set_disconnect_handler(
+      base::BindOnce(&VideoEffectsProcessorImpl::OnMojoDisconnected,
+                     weak_ptr_factory_.GetWeakPtr()));
+  manager_remote_.set_disconnect_handler(
+      base::BindOnce(&VideoEffectsProcessorImpl::OnMojoDisconnected,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
 
-VideoEffectsProcessorImpl::~VideoEffectsProcessorImpl() = default;
+VideoEffectsProcessorImpl::~VideoEffectsProcessorImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (webgpu_context_provider_) {
+    webgpu_context_provider_->RemoveObserver(this);
+  }
+
+  if (raster_interface_context_provider_) {
+    raster_interface_context_provider_->RemoveObserver(this);
+  }
+}
+
+bool VideoEffectsProcessorImpl::Initialize() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK(!initialized_);
+
+  initialized_ = InitializeGpuState();
+  return initialized_;
+}
+
+bool VideoEffectsProcessorImpl::InitializeGpuState() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK(!ContextLossesExceedThreshold(num_context_losses_));
+
+  // In order to create a Video Effects Processor, we will need to have 2
+  // distinct context providers - one for WebGPUInterface, and one for
+  // RasterInterface. We will also need a SharedImageInterface.
+  scoped_refptr<viz::ContextProviderCommandBuffer> webgpu_context_provider =
+      CreateAndBindContextProvider(
+          gpu_channel_host_provider_->GetGpuChannelHost(),
+          gpu::CONTEXT_TYPE_WEBGPU);
+  if (!webgpu_context_provider) {
+    return false;
+  }
+
+  scoped_refptr<viz::ContextProviderCommandBuffer>
+      raster_interface_context_provider = CreateAndBindContextProvider(
+          gpu_channel_host_provider_->GetGpuChannelHost(),
+          gpu::CONTEXT_TYPE_OPENGLES2);
+  if (!raster_interface_context_provider) {
+    return false;
+  }
+
+  scoped_refptr<gpu::ClientSharedImageInterface> shared_image_interface =
+      gpu_channel_host_provider_->GetGpuChannelHost()
+          ->CreateClientSharedImageInterface();
+  if (!shared_image_interface) {
+    return false;
+  }
+
+  webgpu_context_provider_ = std::move(webgpu_context_provider);
+  webgpu_context_provider_->AddObserver(this);
+  raster_interface_context_provider_ =
+      std::move(raster_interface_context_provider);
+  raster_interface_context_provider_->AddObserver(this);
+  shared_image_interface = std::move(shared_image_interface);
+
+  return true;
+}
+
+void VideoEffectsProcessorImpl::OnContextLost() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  initialized_ = false;
+
+  if (webgpu_context_provider_) {
+    webgpu_context_provider_->RemoveObserver(this);
+  }
+
+  webgpu_context_provider_.reset();
+
+  if (raster_interface_context_provider_) {
+    raster_interface_context_provider_->RemoveObserver(this);
+  }
+
+  raster_interface_context_provider_.reset();
+
+  shared_image_interface_.reset();
+
+  ++num_context_losses_;
+  if (ContextLossesExceedThreshold(num_context_losses_)) {
+    if (on_unrecoverable_error_) {
+      std::move(on_unrecoverable_error_).Run();
+    }
+    return;
+  }
+
+  const bool gpu_initialized = InitializeGpuState();
+
+  if (!gpu_initialized && on_unrecoverable_error_) {
+    std::move(on_unrecoverable_error_).Run();
+  }
+}
+
+void VideoEffectsProcessorImpl::OnMojoDisconnected() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // One of the pipes has been disconnected, tear down both and notify the
+  // `on_unrecoverable_error_` since the owner of this processor instance may
+  // want to tear us down (the processor is no longer usable).
+  processor_receiver_.reset();
+  manager_remote_.reset();
+
+  if (on_unrecoverable_error_) {
+    std::move(on_unrecoverable_error_).Run();
+  }
+}
 
 void VideoEffectsProcessorImpl::PostProcess(
     media::mojom::VideoBufferHandlePtr input_frame_data,
@@ -23,6 +202,19 @@
     media::mojom::VideoBufferHandlePtr result_frame_data,
     media::VideoPixelFormat result_pixel_format,
     PostProcessCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!initialized_) {
+    if (ContextLossesExceedThreshold(num_context_losses_)) {
+      std::move(callback).Run(mojom::PostProcessResult::NewError(
+          mojom::PostProcessError::kUnusable));
+    } else {
+      std::move(callback).Run(mojom::PostProcessResult::NewError(
+          mojom::PostProcessError::kNotReady));
+    }
+    return;
+  }
+
   std::move(callback).Run(
       mojom::PostProcessResult::NewError(mojom::PostProcessError::kUnknown));
 }
diff --git a/services/video_effects/video_effects_processor_impl.h b/services/video_effects/video_effects_processor_impl.h
index 8b5d5b1..00562b9 100644
--- a/services/video_effects/video_effects_processor_impl.h
+++ b/services/video_effects/video_effects_processor_impl.h
@@ -5,6 +5,11 @@
 #ifndef SERVICES_VIDEO_EFFECTS_VIDEO_EFFECTS_PROCESSOR_IMPL_H_
 #define SERVICES_VIDEO_EFFECTS_VIDEO_EFFECTS_PROCESSOR_IMPL_H_
 
+#include "base/functional/callback_forward.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/viz/common/gpu/context_lost_observer.h"
 #include "media/base/video_types.h"
 #include "media/capture/mojom/video_capture_buffer.mojom-forward.h"
 #include "media/capture/mojom/video_effects_manager.mojom.h"
@@ -13,17 +18,37 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/video_effects/public/mojom/video_effects_processor.mojom.h"
+#include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
 
 namespace video_effects {
 
-class VideoEffectsProcessorImpl : public mojom::VideoEffectsProcessor {
+class GpuChannelHostProvider;
+
+class VideoEffectsProcessorImpl : public mojom::VideoEffectsProcessor,
+                                  public viz::ContextLostObserver {
  public:
+  // `gpu_channel_host_provider` must outlive this processor.
+  // `on_unrecoverable_error` will be called after an unrecoverable condition
+  // has happened, making this processor defunct. This can happen e.g. when any
+  // of the mojo pipes owned by this processor have been disconnected, or when
+  // the processor was unable to reinitialize GPU resources after context loss.
   explicit VideoEffectsProcessorImpl(
       mojo::PendingRemote<media::mojom::VideoEffectsManager> manager_remote,
-      mojo::PendingReceiver<mojom::VideoEffectsProcessor> processor_receiver);
+      mojo::PendingReceiver<mojom::VideoEffectsProcessor> processor_receiver,
+      GpuChannelHostProvider& gpu_channel_host_provider,
+      base::OnceClosure on_unrecoverable_error);
 
   ~VideoEffectsProcessorImpl() override;
 
+  // Initializes the post-processor. Initialization errors are not recoverable.
+  // Despite that fact, it is guaranteed that initialization errors won't cause
+  // the `on_unrecoverable_error_` to be invoked - this is done to avoid
+  // possible re-entrancy in the caller (e.g. the caller constructs a processor
+  // with an error callback bound to a member method & attempts to initialize
+  // the processor - if an initialization error were to cause the callback to be
+  // invoked, we would re-enter code inside the caller).
+  bool Initialize();
+
   void PostProcess(media::mojom::VideoBufferHandlePtr input_frame_data,
                    media::mojom::VideoFrameInfoPtr input_frame_info,
                    media::mojom::VideoBufferHandlePtr result_frame_data,
@@ -31,8 +56,43 @@
                    PostProcessCallback callback) override;
 
  private:
+  // viz::ContextLostObserver:
+  void OnContextLost() override;
+
+  // Helper, registered as a disconnect handler for `manager_remote_` and
+  // `processor_receiver_`.
+  void OnMojoDisconnected();
+
+  // Helper, initializes GPU state (context providers and shared image
+  // interface).
+  bool InitializeGpuState();
+
+  bool initialized_ = false;
+
   mojo::Remote<media::mojom::VideoEffectsManager> manager_remote_;
   mojo::Receiver<mojom::VideoEffectsProcessor> processor_receiver_;
+
+  raw_ref<GpuChannelHostProvider> gpu_channel_host_provider_;
+
+  // GPU state. Will be created in `Initialize()`, and should be recreated
+  // on context loss.
+  scoped_refptr<viz::ContextProviderCommandBuffer> webgpu_context_provider_;
+  scoped_refptr<viz::ContextProviderCommandBuffer>
+      raster_interface_context_provider_;
+  scoped_refptr<gpu::ClientSharedImageInterface> shared_image_interface_;
+
+  // We'll keep track of how many context losses we've experienced. If this
+  // number is too high, we'll make this processor defunct, assuming that
+  // it is causing some instability with GPU service.
+  int num_context_losses_ = 0;
+
+  // Called when this processor enters a defunct state.
+  base::OnceClosure on_unrecoverable_error_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Must be last:
+  base::WeakPtrFactory<VideoEffectsProcessorImpl> weak_ptr_factory_{this};
 };
 
 }  // namespace video_effects
diff --git a/services/video_effects/video_effects_processor_impl_unittest.cc b/services/video_effects/video_effects_processor_impl_unittest.cc
index 2d0e526..be6a1d3 100644
--- a/services/video_effects/video_effects_processor_impl_unittest.cc
+++ b/services/video_effects/video_effects_processor_impl_unittest.cc
@@ -2,19 +2,29 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <optional>
-
 #include "services/video_effects/video_effects_processor_impl.h"
 
+#include <optional>
+
+#include "base/functional/callback_helpers.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/notreached.h"
+#include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
+#include "gpu/command_buffer/common/capabilities.h"
+#include "gpu/command_buffer/common/context_result.h"
 #include "gpu/command_buffer/common/mailbox_holder.h"
+#include "gpu/ipc/common/gpu_channel.mojom-forward.h"
+#include "gpu/ipc/common/mock_gpu_channel.h"
 #include "media/capture/mojom/video_capture_buffer.mojom.h"
 #include "media/capture/mojom/video_effects_manager.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "services/video_effects/public/mojom/video_effects_processor.mojom.h"
+#include "services/video_effects/test_gpu_channel_host_provider.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -33,20 +43,79 @@
 
 class VideoEffectsProcessorTest : public testing::Test {
   void SetUp() override {
+    gpu_channel_host_provider_.emplace(gpu_channel_);
+
+    on_processor_error_.emplace();
+
     processor_impl_.emplace(manager_receiver_.InitWithNewPipeAndPassRemote(),
-                            processor_remote_.BindNewPipeAndPassReceiver());
+                            processor_remote_.BindNewPipeAndPassReceiver(),
+                            *gpu_channel_host_provider_,
+                            on_processor_error_->GetCallback());
   }
 
  protected:
   base::test::TaskEnvironment task_environment_;
 
-  // Processor under test (remote and impl):
-  mojo::Remote<mojom::VideoEffectsProcessor> processor_remote_;
+  gpu::MockGpuChannel gpu_channel_;
+  std::optional<TestGpuChannelHostProvider> gpu_channel_host_provider_;
+
+  // Processor under test:
   std::optional<VideoEffectsProcessorImpl> processor_impl_;
 
+  std::optional<base::test::TestFuture<void>> on_processor_error_;
+
+  // Processor under test's remote. The unit-tests will usually interact with
+  // the processor via the remote.
+  mojo::Remote<mojom::VideoEffectsProcessor> processor_remote_;
   mojo::PendingReceiver<media::mojom::VideoEffectsManager> manager_receiver_;
 };
 
+TEST_F(VideoEffectsProcessorTest, InitializeSucceeds) {
+  ON_CALL(gpu_channel_, CreateCommandBuffer(_, _, _, _, _, _, _, _))
+      .WillByDefault(
+          [](gpu::mojom::CreateCommandBufferParamsPtr params,
+             int32_t routing_id, base::UnsafeSharedMemoryRegion shared_state,
+             mojo::PendingAssociatedReceiver<gpu::mojom::CommandBuffer>
+                 receiver,
+             mojo::PendingAssociatedRemote<gpu::mojom::CommandBufferClient>
+                 client,
+             gpu::ContextResult* result, gpu::Capabilities* capabilities,
+             gpu::GLCapabilities* gl_capabilities) -> bool {
+            receiver.EnableUnassociatedUsage();
+            *result = gpu::ContextResult::kSuccess;
+            return true;
+          });
+
+  EXPECT_TRUE(processor_impl_->Initialize());
+}
+
+TEST_F(VideoEffectsProcessorTest, ErrorCallbackCalledWhenManagerDisconnects) {
+  manager_receiver_.reset();
+  EXPECT_TRUE(on_processor_error_->Wait());
+}
+
+TEST_F(VideoEffectsProcessorTest, ErrorCallbackCalledWhenProcessorDisconnects) {
+  processor_remote_.reset();
+  EXPECT_TRUE(on_processor_error_->Wait());
+}
+
+// TODO(b/333097635): Figure out how to mock/fake the GpuChannel so that it
+// does not raise context loss events immediately after creating context
+// providers.
+TEST_F(VideoEffectsProcessorTest,
+       DISABLED_ErrorCallbackCalledContextRecreationFailed) {
+  // Initialize the processor first, GPU service connections won't be
+  // established until the processor has been initialized and thus it won't
+  // auto-recover from GPU context loss.
+  ASSERT_TRUE(processor_impl_->Initialize());
+}
+
+// TODO(b/333097635): Figure out how to mock/fake the GpuChannel so that it
+// does not raise context loss events immediately after creating context
+// providers.
+TEST_F(VideoEffectsProcessorTest,
+       DISABLED_ProcessorGivesUpAfterTooManyContextLosses) {}
+
 TEST_F(VideoEffectsProcessorTest, PostProcessRunsAndFails) {
   // For now, since `VideoEffectsProcessorImpl` is still a stub, we expect a
   // call to `VideoEffectsProcessor::PostProcess()` to fail.
diff --git a/services/video_effects/video_effects_service_impl.cc b/services/video_effects/video_effects_service_impl.cc
index 7c93284..e5702a0 100644
--- a/services/video_effects/video_effects_service_impl.cc
+++ b/services/video_effects/video_effects_service_impl.cc
@@ -8,12 +8,14 @@
 #include <utility>
 
 #include "base/check.h"
+#include "base/functional/bind.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
 #include "media/capture/mojom/video_effects_manager.mojom-forward.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "services/video_effects/public/mojom/video_effects_processor.mojom-forward.h"
 #include "services/video_effects/public/mojom/video_effects_service.mojom.h"
 #include "services/video_effects/video_effects_processor_impl.h"
-#include "services/viz/public/cpp/gpu/gpu.h"
 
 namespace video_effects {
 
@@ -25,21 +27,42 @@
   CHECK(gpu_channel_host_provider_);
 }
 
-VideoEffectsServiceImpl::~VideoEffectsServiceImpl() = default;
+VideoEffectsServiceImpl::~VideoEffectsServiceImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
 
 void VideoEffectsServiceImpl::CreateEffectsProcessor(
     const std::string& device_id,
-    mojo::PendingRemote<media::mojom::VideoEffectsManager> manager,
-    mojo::PendingReceiver<mojom::VideoEffectsProcessor> processor) {
+    mojo::PendingRemote<media::mojom::VideoEffectsManager> manager_remote,
+    mojo::PendingReceiver<mojom::VideoEffectsProcessor> processor_receiver) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   if (processors_.contains(device_id)) {
     return;
   }
 
-  std::unique_ptr<VideoEffectsProcessorImpl> effects_processor =
-      std::make_unique<VideoEffectsProcessorImpl>(std::move(manager),
-                                                  std::move(processor));
+  auto on_unrecoverable_processor_error =
+      base::BindOnce(&VideoEffectsServiceImpl::RemoveProcessor,
+                     weak_ptr_factory_.GetWeakPtr(), device_id);
 
-  processors_.insert(std::make_pair(device_id, std::move(effects_processor)));
+  auto effects_processor = std::make_unique<VideoEffectsProcessorImpl>(
+      std::move(manager_remote), std::move(processor_receiver),
+      *gpu_channel_host_provider_.get(),
+      std::move(on_unrecoverable_processor_error));
+
+  if (!effects_processor->Initialize()) {
+    return;
+  }
+
+  auto [_, inserted] = processors_.insert(
+      std::make_pair(device_id, std::move(effects_processor)));
+  CHECK(inserted);
+}
+
+void VideoEffectsServiceImpl::RemoveProcessor(const std::string& id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  processors_.erase(id);
 }
 
 }  // namespace video_effects
diff --git a/services/video_effects/video_effects_service_impl.h b/services/video_effects/video_effects_service_impl.h
index 67d4e29..9f9e65b 100644
--- a/services/video_effects/video_effects_service_impl.h
+++ b/services/video_effects/video_effects_service_impl.h
@@ -10,6 +10,8 @@
 
 #include "base/containers/flat_map.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
 #include "gpu/ipc/client/gpu_channel_host.h"
 #include "media/capture/mojom/video_effects_manager.mojom-forward.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -30,6 +32,9 @@
  public:
   virtual ~GpuChannelHostProvider() = default;
 
+  // Return a connected `gpu::GpuChannelHost`. Implementations should expect
+  // this method to be called somewhat frequently when a new Video Effects
+  // Processor is created.
   virtual scoped_refptr<gpu::GpuChannelHost> GetGpuChannelHost() = 0;
 };
 
@@ -54,6 +59,10 @@
       mojo::PendingReceiver<mojom::VideoEffectsProcessor> processor) override;
 
  private:
+  // Helper - used to clean up instances of `VideoEffectsProcessor`s that are
+  // no longer functional.
+  void RemoveProcessor(const std::string& id);
+
   // Mapping from the device ID to processor implementation. Device ID is only
   // used to deduplicate processor creation requests.
   base::flat_map<std::string, std::unique_ptr<VideoEffectsProcessorImpl>>
@@ -61,6 +70,11 @@
 
   mojo::Receiver<mojom::VideoEffectsService> receiver_;
   std::unique_ptr<GpuChannelHostProvider> gpu_channel_host_provider_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Must be last:
+  base::WeakPtrFactory<VideoEffectsServiceImpl> weak_ptr_factory_{this};
 };
 
 }  // namespace video_effects
diff --git a/services/video_effects/video_effects_service_impl_unittest.cc b/services/video_effects/video_effects_service_impl_unittest.cc
index 499f2ac9f..3d62823 100644
--- a/services/video_effects/video_effects_service_impl_unittest.cc
+++ b/services/video_effects/video_effects_service_impl_unittest.cc
@@ -2,17 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <optional>
-
 #include "services/video_effects/video_effects_service_impl.h"
 
+#include <optional>
+
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
+#include "gpu/ipc/common/mock_gpu_channel.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "services/video_effects/public/mojom/video_effects_processor.mojom.h"
 #include "services/video_effects/public/mojom/video_effects_service.mojom.h"
+#include "services/video_effects/test_gpu_channel_host_provider.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -24,32 +26,29 @@
 
 constexpr char kDeviceId[] = "test_device";
 
-class TestGpuChannelHostProvider : public GpuChannelHostProvider {
- public:
-  TestGpuChannelHostProvider() = default;
-
-  scoped_refptr<gpu::GpuChannelHost> GetGpuChannelHost() override {
-    return nullptr;
-  }
-};
-
 }  // namespace
 
 class VideoEffectsServiceTest : public testing::Test {
   void SetUp() override {
-    service_impl_.emplace(service_remote_.BindNewPipeAndPassReceiver(),
-                          std::make_unique<TestGpuChannelHostProvider>());
+    service_impl_.emplace(
+        service_remote_.BindNewPipeAndPassReceiver(),
+        std::make_unique<TestGpuChannelHostProvider>(gpu_channel_));
   }
 
  protected:
   base::test::TaskEnvironment task_environment_;
 
+  gpu::MockGpuChannel gpu_channel_;
+
   // Service under test (remote and impl):
   mojo::Remote<mojom::VideoEffectsService> service_remote_;
   std::optional<VideoEffectsServiceImpl> service_impl_;
 };
 
-TEST_F(VideoEffectsServiceTest, CreateEffectsProcessorWorks) {
+// TODO(b/333097635): Figure out how to mock/fake the GpuChannel so that it
+// does not raise context loss events immediately after creating context
+// providers.
+TEST_F(VideoEffectsServiceTest, DISABLED_CreateEffectsProcessorWorks) {
   // Calling into `VideoEffectsService:::CreateEffectsProcessor()` is expected
   // to work (irrespective of whether the passed-in pipes are usable or not).
 
@@ -64,7 +63,11 @@
   EXPECT_TRUE(processor_remote.is_connected());
 }
 
-TEST_F(VideoEffectsServiceTest, CreateEffectsProcessorWithSameIdFails) {
+// TODO(b/333097635): Figure out how to mock/fake the GpuChannel so that it
+// does not raise context loss events immediately after creating context
+// providers.
+TEST_F(VideoEffectsServiceTest,
+       DISABLED_CreateEffectsProcessorWithSameIdFails) {
   // Calling into `VideoEffectsService:::CreateEffectsProcessor()` is expected
   // to fail if the same device id is passed.
 
@@ -90,4 +93,37 @@
   EXPECT_FALSE(processor_remote2.is_connected());
 }
 
+// TODO(b/333097635): Figure out how to mock/fake the GpuChannel so that it
+// does not raise context loss events immediately after creating context
+// providers.
+TEST_F(VideoEffectsServiceTest,
+       DISABLED_RecreateEffectsProcessorWithSameIdSucceeds) {
+  // Calling into `VideoEffectsService:::CreateEffectsProcessor()` is expected
+  // to succeed if the previous processor with that ID has been removed
+
+  mojo::PendingReceiver<media::mojom::VideoEffectsManager> manager_receiver1;
+  mojo::Remote<mojom::VideoEffectsProcessor> processor_remote1;
+
+  service_remote_->CreateEffectsProcessor(
+      kDeviceId, manager_receiver1.InitWithNewPipeAndPassRemote(),
+      processor_remote1.BindNewPipeAndPassReceiver());
+
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(processor_remote1.is_connected());
+
+  // Disconnect the first processor.
+  processor_remote1.reset();
+  base::RunLoop().RunUntilIdle();
+
+  mojo::PendingReceiver<media::mojom::VideoEffectsManager> manager_receiver2;
+  mojo::Remote<mojom::VideoEffectsProcessor> processor_remote2;
+
+  service_remote_->CreateEffectsProcessor(
+      kDeviceId, manager_receiver2.InitWithNewPipeAndPassRemote(),
+      processor_remote2.BindNewPipeAndPassReceiver());
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(processor_remote2.is_connected());
+}
+
 }  // namespace video_effects
diff --git a/services/video_effects/viz_gpu_channel_host_provider.cc b/services/video_effects/viz_gpu_channel_host_provider.cc
new file mode 100644
index 0000000..b6ba4dc
--- /dev/null
+++ b/services/video_effects/viz_gpu_channel_host_provider.cc
@@ -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.
+
+#include "services/video_effects/viz_gpu_channel_host_provider.h"
+
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "gpu/ipc/client/gpu_channel_host.h"
+#include "services/video_effects/video_effects_service_impl.h"
+#include "services/viz/public/cpp/gpu/gpu.h"
+
+namespace video_effects {
+
+VizGpuChannelHostProvider::VizGpuChannelHostProvider(
+    std::unique_ptr<viz::Gpu> viz_gpu)
+    : viz_gpu_(std::move(viz_gpu)) {
+  CHECK(viz_gpu_);
+}
+
+VizGpuChannelHostProvider::~VizGpuChannelHostProvider() = default;
+
+scoped_refptr<gpu::GpuChannelHost>
+VizGpuChannelHostProvider::GetGpuChannelHost() {
+  scoped_refptr<gpu::GpuChannelHost> result = viz_gpu_->GetGpuChannel();
+  if (!result || result->IsLost()) {
+    return viz_gpu_->EstablishGpuChannelSync();
+  }
+  return result;
+}
+
+}  // namespace video_effects
diff --git a/services/video_effects/viz_gpu_channel_host_provider.h b/services/video_effects/viz_gpu_channel_host_provider.h
new file mode 100644
index 0000000..84d36d9
--- /dev/null
+++ b/services/video_effects/viz_gpu_channel_host_provider.h
@@ -0,0 +1,30 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIDEO_EFFECTS_VIZ_GPU_CHANNEL_HOST_PROVIDER_H_
+#define SERVICES_VIDEO_EFFECTS_VIZ_GPU_CHANNEL_HOST_PROVIDER_H_
+
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "gpu/ipc/client/gpu_channel_host.h"
+#include "services/video_effects/video_effects_service_impl.h"
+#include "services/viz/public/cpp/gpu/gpu.h"
+
+namespace video_effects {
+
+class VizGpuChannelHostProvider : public video_effects::GpuChannelHostProvider {
+ public:
+  explicit VizGpuChannelHostProvider(std::unique_ptr<viz::Gpu> viz_gpu);
+  ~VizGpuChannelHostProvider() override;
+
+  scoped_refptr<gpu::GpuChannelHost> GetGpuChannelHost() override;
+
+ private:
+  std::unique_ptr<viz::Gpu> viz_gpu_;
+};
+
+}  // namespace video_effects
+
+#endif  // SERVICES_VIDEO_EFFECTS_VIZ_GPU_CHANNEL_HOST_PROVIDER_H_
diff --git a/services/webnn/BUILD.gn b/services/webnn/BUILD.gn
index 6d6fd41..ba69312 100644
--- a/services/webnn/BUILD.gn
+++ b/services/webnn/BUILD.gn
@@ -42,6 +42,10 @@
   ]
 }
 
+source_set("webnn_switches") {
+  sources = [ "webnn_switches.h" ]
+}
+
 component("webnn_service") {
   sources = [
     "error.h",
@@ -59,6 +63,7 @@
 
   deps = [
     ":buildflags",
+    ":webnn_switches",
     ":webnn_utils",
     "//base",
     "//components/ml/webnn",
diff --git a/services/webnn/coreml/graph_builder.cc b/services/webnn/coreml/graph_builder.cc
index c02f4e8..f12184b 100644
--- a/services/webnn/coreml/graph_builder.cc
+++ b/services/webnn/coreml/graph_builder.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 #include <fstream>
+#include <memory>
 #include <optional>
 #include <string>
 #include <string_view>
@@ -424,7 +425,7 @@
 }
 
 // static
-[[nodiscard]] base::expected<std::unique_ptr<GraphBuilder>, mojom::ErrorPtr>
+base::expected<std::unique_ptr<GraphBuilder::Result>, mojom::ErrorPtr>
 GraphBuilder::CreateAndBuild(const mojom::GraphInfo& graph_info,
                              const base::FilePath& working_directory) {
   // Use a random string for the model package directory, because MLModel
@@ -437,28 +438,21 @@
 
   base::FilePath data_dir = ml_package_dir.Append(kMlPackageDataDir);
 
-  auto graph_builder = base::WrapUnique(new GraphBuilder(
-      graph_info, std::move(ml_package_dir),
-      data_dir.Append(kMlPackageModelFileName),
-      data_dir.Append(kMlPackageWeightsDir).Append(kMlPackageWeightsFileName)));
+  GraphBuilder graph_builder(graph_info, std::move(ml_package_dir));
 
-  RETURN_IF_ERROR(graph_builder->BuildCoreMLModel());
+  RETURN_IF_ERROR(graph_builder.BuildCoreMLModel());
 
-  if (!graph_builder->SerializeModel()) {
+  if (!graph_builder.SerializeModel()) {
     return NewUnknownError("Failed to serialize CoreML model.");
   }
 
-  return graph_builder;
+  return graph_builder.FinishAndTakeResult();
 }
 
 GraphBuilder::GraphBuilder(const mojom::GraphInfo& graph_info,
-                           base::FilePath ml_package_dir,
-                           base::FilePath model_file_path,
-                           base::FilePath weights_file_path)
+                           base::FilePath ml_package_dir)
     : graph_info_(graph_info),
-      ml_package_dir_(std::move(ml_package_dir)),
-      model_file_path_(std::move(model_file_path)),
-      weights_file_path_(std::move(weights_file_path)) {}
+      result_(std::make_unique<Result>(std::move(ml_package_dir))) {}
 
 GraphBuilder::~GraphBuilder() = default;
 
@@ -589,7 +583,10 @@
 bool GraphBuilder::SerializeModel() {
   base::ElapsedTimer ml_model_write_timer;
   // This will always overwrite if there is an existing file.
-  std::fstream model_file(model_file_path_.value(),
+  std::fstream model_file(ml_package_dir()
+                              .Append(kMlPackageDataDir)
+                              .Append(kMlPackageModelFileName)
+                              .value(),
                           std::ios::out | std::ios::binary);
   bool result = ml_model_.SerializeToOstream(&model_file);
   UMA_HISTOGRAM_MEDIUM_TIMES("WebNN.CoreML.TimingMs.MLModelWrite",
@@ -597,9 +594,16 @@
   return result;
 }
 
+std::unique_ptr<GraphBuilder::Result> GraphBuilder::FinishAndTakeResult() {
+  return std::move(result_);
+}
+
 base::expected<void, mojom::ErrorPtr> GraphBuilder::WriteWeightsToFile(
     CoreML::Specification::MILSpec::Block& block) {
-  base::File weights_file(weights_file_path_,
+  base::File weights_file(ml_package_dir()
+                              .Append(kMlPackageDataDir)
+                              .Append(kMlPackageWeightsDir)
+                              .Append(kMlPackageWeightsFileName),
                           base::File::FLAG_CREATE | base::File::FLAG_WRITE);
 
   uint64_t current_offset = 0;
@@ -647,21 +651,6 @@
   return base::ok();
 }
 
-const mojom::Operand& GraphBuilder::GetOperand(uint64_t operand_id) const {
-  return *graph_info_->id_to_operand_map.at(operand_id);
-}
-
-const GraphBuilder::OperandInfo& GraphBuilder::FindInputOperandInfo(
-    const std::string& input_name) const {
-  auto id = input_name_to_id_map_.find(input_name);
-  CHECK(id != input_name_to_id_map_.end());
-  return GetOperandInfo(id->second);
-}
-
-const base::FilePath& GraphBuilder::GetModelFilePath() {
-  return ml_package_dir_;
-}
-
 void GraphBuilder::AddPlaceholderInput(
     CoreML::Specification::MILSpec::Function& main_function,
     CoreML::Specification::MILSpec::Block& block) {
@@ -715,14 +704,15 @@
       *main_function.add_inputs();
   PopulateNamedValueType(input_id, input);
 
-  CHECK(input_name_to_id_map_.try_emplace(operand.name.value(), input_id)
+  CHECK(input_name_to_id_map()
+            .try_emplace(operand.name.value(), input_id)
             .second);
   return base::ok();
 }
 
 [[nodiscard]] base::expected<void, mojom::ErrorPtr> GraphBuilder::AddOutput(
     uint64_t output_id) {
-  CHECK(id_to_op_input_info_map_.contains(output_id));
+  CHECK(id_to_operand_info_map().contains(output_id));
   auto* mutable_description = ml_model_.mutable_description();
   auto* feature_description = mutable_description->add_output();
   RETURN_IF_ERROR(PopulateFeatureDescription(output_id, *feature_description));
@@ -1300,9 +1290,13 @@
   attributes["val"] = std::move(blob_value);
 }
 
+const mojom::Operand& GraphBuilder::GetOperand(uint64_t operand_id) const {
+  return *graph_info_->id_to_operand_map.at(operand_id);
+}
+
 [[nodiscard]] const GraphBuilder::OperandInfo& GraphBuilder::GetOperandInfo(
     uint64_t operand_id) const {
-  return id_to_op_input_info_map_.at(operand_id);
+  return result_->GetOperandInfo(operand_id);
 }
 
 base::expected<void, mojom::ErrorPtr> GraphBuilder::PopulateFeatureDescription(
@@ -1385,7 +1379,7 @@
   const mojom::Operand& operand = GetOperand(operand_id);
   const CoreML::Specification::MILSpec::DataType mil_data_type =
       OperandTypeToMILDataType(operand.data_type);
-  CHECK(id_to_op_input_info_map_
+  CHECK(id_to_operand_info_map()
             .try_emplace(operand_id,
                          OperandInfo(GetCoreMLNameFromOperand(operand_id),
                                      operand.dimensions.empty()
@@ -1428,10 +1422,10 @@
 
 base::expected<void, mojom::ErrorPtr>
 GraphBuilder::SetupMlPackageDirStructure() {
-  if (!base::CreateDirectory(ml_package_dir_)) {
+  if (!base::CreateDirectory(ml_package_dir())) {
     return NewUnknownError("Fail to create .mlpackage directory.");
   }
-  base::FilePath data_dir = ml_package_dir_.Append(kMlPackageDataDir);
+  base::FilePath data_dir = ml_package_dir().Append(kMlPackageDataDir);
   if (!base::CreateDirectory(data_dir)) {
     return NewUnknownError("Fail to create .mlpackage/Data directory.");
   }
@@ -1470,7 +1464,8 @@
   metadata.Set(kManifestItemInfoEntriesKey, std::move(item_info_entries));
   metadata.Set(kManifestVersionKey, kManifestVersionValue);
   metadata.Set(kManifestModelIdentifierKey, model_identifier);
-  JSONFileValueSerializer serializer(ml_package_dir_.Append(kManifestFileName));
+  JSONFileValueSerializer serializer(
+      ml_package_dir().Append(kManifestFileName));
   if (!serializer.Serialize(std::move(metadata))) {
     return NewUnknownError("Fail to create Manifest.json for mlpackage.");
   }
@@ -1509,17 +1504,39 @@
       kStringSeparator);
 }
 GraphBuilder::OperandInfo::OperandInfo(
-    std::string coreml_name, std::vector<uint32_t> dimensions,
+    std::string coreml_name,
+    std::vector<uint32_t> dimensions,
     mojom::Operand::DataType data_type,
     CoreML::Specification::MILSpec::DataType mil_data_type)
     : coreml_name(std::move(coreml_name)),
       dimensions(std::move(dimensions)),
       data_type(data_type),
-      mil_data_type(std::move(mil_data_type)) {}
+      mil_data_type(mil_data_type) {}
 
 GraphBuilder::OperandInfo::OperandInfo() = default;
 GraphBuilder::OperandInfo::~OperandInfo() = default;
 GraphBuilder::OperandInfo::OperandInfo(OperandInfo&) = default;
 GraphBuilder::OperandInfo::OperandInfo(OperandInfo&&) = default;
 
+GraphBuilder::Result::Result(base::FilePath ml_package_dir)
+    : ml_package_dir(std::move(ml_package_dir)) {}
+GraphBuilder::Result::~Result() = default;
+
+const GraphBuilder::OperandInfo& GraphBuilder::Result::FindInputOperandInfo(
+    const std::string& input_name) const {
+  auto it = input_name_to_id_map.find(input_name);
+  return GetOperandInfo(it->second);
+}
+
+const base::FilePath& GraphBuilder::Result::GetModelFilePath() {
+  return ml_package_dir;
+}
+
+const GraphBuilder::OperandInfo& GraphBuilder::Result::GetOperandInfo(
+    uint64_t operand_id) const {
+  auto it = id_to_operand_info_map.find(operand_id);
+  CHECK(it != id_to_operand_info_map.end());
+  return it->second;
+}
+
 }  // namespace webnn::coreml
diff --git a/services/webnn/coreml/graph_builder.h b/services/webnn/coreml/graph_builder.h
index 78fdfc3..f9ad507f 100644
--- a/services/webnn/coreml/graph_builder.h
+++ b/services/webnn/coreml/graph_builder.h
@@ -6,11 +6,13 @@
 #define SERVICES_WEBNN_COREML_GRAPH_BUILDER_H_
 
 #include <cstdint>
+#include <memory>
 #include <string_view>
 
 #include "base/containers/flat_map.h"
 #include "base/files/file_path.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/stack_allocated.h"
 #include "base/types/expected.h"
 #include "services/webnn/public/mojom/webnn_error.mojom-forward.h"
 #include "services/webnn/public/mojom/webnn_graph.mojom.h"
@@ -51,20 +53,13 @@
 // Reads the WebNN graph from the mojom::GraphInfo to
 // produce CoreML model and serializes to provided `working_directory`.
 // There is nothing macOS-specific in this class.
+//
+// The instances of the class may not be allocated on the heap, but as a member
+// variable of a non-stack-allocated class and be single-use per conversion.
 class GraphBuilder {
+  STACK_ALLOCATED();
+
  public:
-  // Factory method that creates a GraphBuilder, builds and serializes the
-  // CoreML model to the `working_directory`. This expects the
-  // `working_directory` to be an empty directory.
-  //
-  // Returns unexpected if it fails.
-  [[nodiscard]] static base::expected<std::unique_ptr<GraphBuilder>,
-                                      mojom::ErrorPtr>
-  CreateAndBuild(const mojom::GraphInfo& graph_info,
-                 const base::FilePath& working_directory);
-
-  ~GraphBuilder();
-
   // Tracks Operand information during graph building, so that
   // future operations can look them up based on operand id.
   //
@@ -86,15 +81,43 @@
     CoreML::Specification::MILSpec::DataType mil_data_type;
   };
 
-  // This method must be called with an `input_name` which corresponds to some
-  // input, or else it will crash.
-  const OperandInfo& FindInputOperandInfo(const std::string& input_name) const;
-  const base::FilePath& GetModelFilePath();
+  struct Result {
+    explicit Result(base::FilePath ml_package_dir);
+    Result(const Result&) = delete;
+    Result& operator=(const Result&) = delete;
+    ~Result();
+
+    // This method must be called with an `input_name` which corresponds to some
+    // input, or else it will crash.
+    const OperandInfo& FindInputOperandInfo(
+        const std::string& input_name) const;
+    const base::FilePath& GetModelFilePath();
+
+    [[nodiscard]] const OperandInfo& GetOperandInfo(uint64_t operand_id) const;
+
+    const base::FilePath ml_package_dir;
+    // Used to get operand info to specify input for a MILSpec::Operation.
+    std::map<std::string, uint64_t> input_name_to_id_map;
+    std::map<uint64_t, OperandInfo> id_to_operand_info_map;
+  };
+
+  // Factory method that creates a GraphBuilder, builds and serializes the
+  // CoreML model to the `working_directory`. This expects the
+  // `working_directory` to be an empty directory.
+  //
+  // Returns unexpected if it fails.
+  [[nodiscard]] static base::expected<std::unique_ptr<Result>, mojom::ErrorPtr>
+  CreateAndBuild(const mojom::GraphInfo& graph_info,
+                 const base::FilePath& working_directory);
+
+  GraphBuilder(const GraphBuilder&) = delete;
+  GraphBuilder& operator=(const GraphBuilder&) = delete;
+
+  ~GraphBuilder();
 
  private:
   GraphBuilder(const mojom::GraphInfo& graph_info,
-               base::FilePath ml_package_dir, base::FilePath model_file_path,
-               base::FilePath weights_file_path);
+               base::FilePath ml_package_dir);
 
   [[nodiscard]] base::expected<void, mojom::ErrorPtr> BuildCoreMLModel();
 
@@ -102,9 +125,8 @@
   [[nodiscard]] base::expected<void, mojom::ErrorPtr> WriteWeightsToFile(
       CoreML::Specification::MILSpec::Block& block);
 
-  // Returns the Operand corresponding to an `operand_id` from `graph_info_`.
-  // Will crash if `graph_info_` does not contain `operand_id`.
-  const mojom::Operand& GetOperand(uint64_t operand_id) const;
+  // No further methods may be called on this class after calling this method.
+  [[nodiscard]] std::unique_ptr<Result> FinishAndTakeResult();
 
   // Add input in Model.description and in Program's main function inputs.
   [[nodiscard]] base::expected<void, mojom::ErrorPtr> AddInput(
@@ -165,13 +187,25 @@
                             uint64_t offset,
                             CoreML::Specification::MILSpec::Block& block);
 
-  // Helpers
+  // Helpers.
+  const mojom::Operand& GetOperand(uint64_t operand_id) const;
   [[nodiscard]] const OperandInfo& GetOperandInfo(uint64_t operand_id) const;
   [[nodiscard]] base::expected<void, mojom::ErrorPtr>
   PopulateFeatureDescription(
       uint64_t operand_id,
       ::CoreML::Specification::FeatureDescription& feature_description);
 
+  // Accessors for fields declared in `result_`.
+  const base::FilePath& ml_package_dir() const {
+    return result_->ml_package_dir;
+  }
+  std::map<std::string, uint64_t>& input_name_to_id_map() const {
+    return result_->input_name_to_id_map;
+  }
+  std::map<uint64_t, OperandInfo>& id_to_operand_info_map() const {
+    return result_->id_to_operand_info_map;
+  }
+
   // MILSpec::Program's Function, Block, Operation's inputs/outputs could be
   // defined as NamedValueType.
   void PopulateNamedValueType(
@@ -209,12 +243,8 @@
 
   CoreML::Specification::Model ml_model_;
   raw_ptr<CoreML::Specification::MILSpec::Program> program_;
-  // Used to get operand info to specify input for a MILSpec::Operation.
-  std::map<uint64_t, OperandInfo> id_to_op_input_info_map_;
-  std::map<std::string, uint64_t> input_name_to_id_map_;
-  const base::FilePath ml_package_dir_;
-  const base::FilePath model_file_path_;
-  const base::FilePath weights_file_path_;
+
+  std::unique_ptr<Result> result_;
 };
 
 }  // namespace webnn::coreml
diff --git a/services/webnn/coreml/graph_impl.mm b/services/webnn/coreml/graph_impl.mm
index 6b6dbc6..95606625 100644
--- a/services/webnn/coreml/graph_impl.mm
+++ b/services/webnn/coreml/graph_impl.mm
@@ -10,8 +10,10 @@
 
 #include "base/apple/foundation_util.h"
 #include "base/barrier_closure.h"
+#include "base/command_line.h"
 #include "base/files/file.h"
 #include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/checked_math.h"
 #include "base/strings/string_number_conversions.h"
@@ -23,6 +25,7 @@
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "services/webnn/coreml/graph_builder.h"
 #include "services/webnn/error.h"
+#include "services/webnn/webnn_switches.h"
 
 @interface WebNNMLFeatureProvider : NSObject <MLFeatureProvider>
 - (MLFeatureValue*)featureValueForName:(NSString*)featureName;
@@ -79,7 +82,7 @@
   base::ElapsedTimer ml_model_write_timer;
   // Generate .mlpackage.
   ASSIGN_OR_RETURN(
-      std::unique_ptr<GraphBuilder> graph_builder,
+      std::unique_ptr<GraphBuilder::Result> build_graph_result,
       GraphBuilder::CreateAndBuild(*graph_info.get(), model_file_dir.GetPath()),
       [&](mojom::ErrorPtr error) {
         originating_sequence->PostTask(
@@ -101,7 +104,7 @@
   for (auto const& [name, size] :
        compute_resource_info.input_name_to_byte_length_map) {
     std::optional<GraphImpl::CoreMLFeatureInfo> coreml_feature_info =
-        GetCoreMLFeatureInfo(graph_builder->FindInputOperandInfo(name));
+        GetCoreMLFeatureInfo(build_graph_result->FindInputOperandInfo(name));
     if (!coreml_feature_info.has_value()) {
       originating_sequence->PostTask(
           FROM_HERE,
@@ -136,7 +139,7 @@
 
   [MLModel
       compileModelAtURL:base::apple::FilePathToNSURL(
-                            graph_builder->GetModelFilePath())
+                            build_graph_result->GetModelFilePath())
       completionHandler:^(NSURL* compiled_model_url, NSError* error) {
         UMA_HISTOGRAM_MEDIUM_TIMES(
             "WebNN.CoreML.TimingMs.MLModelCompile",
@@ -404,9 +407,29 @@
       callback(std::move(callback)) {}
 
 GraphImpl::CompilationContext::~CompilationContext() {
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kWebNNCoreMlDumpModel)) {
+    const auto dump_directory =
+        base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
+            switches::kWebNNCoreMlDumpModel);
+    LOG(INFO) << "webnn::coreml Copying model files to " << dump_directory;
+    if (dump_directory.empty()) {
+      LOG(ERROR) << "webnn::coreml Dump directory not specified.";
+    } else {
+      if (!model_file_dir.IsValid() ||
+          !base::CopyDirectory(model_file_dir.GetPath(), dump_directory,
+                               /*recursive=*/true)) {
+        LOG(ERROR) << "webnn::coreml Failed to copy model file directory.";
+      }
+      if (!compiled_model_dir.IsValid() ||
+          !base::CopyDirectory(compiled_model_dir.GetPath(), dump_directory,
+                               /*recursive=*/true)) {
+        LOG(ERROR) << "webnn::coreml Failed to copy compiled model directory.";
+      }
+    }
+  }
   // Though the destructors of ScopedTempDir will delete these directories.
   // Explicitly delete them here to check for success.
-  // TODO(https://crbug.com/1522278): Add debug flag to skip cleanup.
   if (model_file_dir.IsValid()) {
     CHECK(model_file_dir.Delete());
   }
diff --git a/services/webnn/tflite/graph_builder.cc b/services/webnn/tflite/graph_builder.cc
index c00c906..c4bc6c2 100644
--- a/services/webnn/tflite/graph_builder.cc
+++ b/services/webnn/tflite/graph_builder.cc
@@ -328,6 +328,10 @@
       ASSIGN_OR_RETURN(operator_offset, SerializeElu(*op.get_elu()));
       break;
     }
+    case mojom::Operation::Tag::kGather: {
+      ASSIGN_OR_RETURN(operator_offset, SerializeGather(*op.get_gather()));
+      break;
+    }
     case mojom::Operation::Tag::kGemm: {
       ASSIGN_OR_RETURN(operator_offset, SerializeGemm(*op.get_gemm()));
       break;
@@ -379,8 +383,6 @@
       return base::unexpected("batchNormalization is not implemented");
     case mojom::Operation::Tag::kExpand:
       return base::unexpected("expand is not implemented");
-    case mojom::Operation::Tag::kGather:
-      return base::unexpected("gather is not implemented");
     case mojom::Operation::Tag::kGru:
       return base::unexpected("gru is not implemented");
     case mojom::Operation::Tag::kGruCell:
@@ -989,6 +991,51 @@
                                  elu.input_operand_id, elu.output_operand_id);
 }
 
+auto GraphBuilder::SerializeGather(const mojom::Gather& gather)
+    -> base::expected<OperatorOffset, std::string> {
+  // The WebNN indices must be one of type uint32 or int64, but TFLite indices
+  // need int32 or int64 type, so a cast operation need to be inserted before
+  // Gather if indices data type is uint32.
+  int32_t indices_tensor_index =
+      operand_to_index_map_.at(gather.indices_operand_id);
+  const mojom::Operand& indices_operand = GetOperand(gather.indices_operand_id);
+  if (indices_operand.data_type == mojom::Operand::DataType::kUint32) {
+    ASSIGN_OR_RETURN(const std::vector<int32_t> signed_indices_dimensions,
+                     ToSignedDimensions(indices_operand.dimensions));
+    indices_tensor_index = base::checked_cast<int32_t>(tensors_.size());
+    tensors_.emplace_back(::tflite::CreateTensor(
+        builder_, builder_.CreateVector<int32_t>(signed_indices_dimensions),
+        ::tflite::TensorType_INT64));
+
+    operators_.emplace_back(SerializeCastOperation(
+        operand_to_index_map_.at(gather.indices_operand_id),
+        /*input_tensor_type=*/::tflite::TensorType_UINT32, indices_tensor_index,
+        /*output_tensor_type=*/::tflite::TensorType_INT64));
+  } else {
+    CHECK_EQ(indices_operand.data_type, mojom::Operand::DataType::kInt64);
+  }
+
+  // The WebNN axis option is uint32 data type, but TFLite axis needs int32
+  // type, so the axis need to be validated here to not overflow.
+  auto checked_axis = base::MakeCheckedNum<int32_t>(gather.axis);
+  if (!checked_axis.IsValid()) {
+    return base::unexpected("The axis in gather operation is too large.");
+  }
+  const auto gather_options =
+      ::tflite::CreateGatherOptions(builder_, checked_axis.ValueOrDie());
+
+  const uint32_t operator_code_index =
+      GetOperatorCodeIndex(::tflite::BuiltinOperator_GATHER);
+  const std::array<int32_t, 2> op_inputs = {
+      operand_to_index_map_.at(gather.input_operand_id), indices_tensor_index};
+  const std::array<int32_t, 1> op_outputs = {
+      operand_to_index_map_.at(gather.output_operand_id)};
+  return ::tflite::CreateOperator(
+      builder_, operator_code_index, builder_.CreateVector<int32_t>(op_inputs),
+      builder_.CreateVector<int32_t>(op_outputs),
+      ::tflite::BuiltinOptions_GatherOptions, gather_options.Union());
+}
+
 auto GraphBuilder::SerializeGemm(const mojom::Gemm& gemm)
     -> base::expected<OperatorOffset, std::string> {
   // Check for unsupported inputs.
diff --git a/services/webnn/tflite/graph_builder.h b/services/webnn/tflite/graph_builder.h
index 94290da..8d210c8 100644
--- a/services/webnn/tflite/graph_builder.h
+++ b/services/webnn/tflite/graph_builder.h
@@ -42,7 +42,7 @@
 // serialize.
 //
 // The instances of the class may not be allocated on the heap, but as a member
-// variable of a non-stack-allocated and be single-use per conversion.
+// variable of a non-stack-allocated class and be single-use per conversion.
 class GraphBuilder final {
   STACK_ALLOCATED();
 
@@ -159,6 +159,8 @@
       const mojom::ElementWiseUnary& op);
   base::expected<OperatorOffset, std::string> SerializeElu(
       const mojom::Elu& elu);
+  base::expected<OperatorOffset, std::string> SerializeGather(
+      const mojom::Gather& gather);
   base::expected<OperatorOffset, std::string> SerializeGemm(
       const mojom::Gemm& gemm);
   OperatorOffset SerializeHardSwish(const mojom::HardSwish& hard_swish);
@@ -182,9 +184,8 @@
       const mojom::Split& split);
   OperatorOffset SerializeTranspose(const mojom::Transpose& transpose);
 
-  // There are no further methods should be called on this class after this
-  // function because the buffer of `buffer_` is now owned by the detached
-  // buffer.
+  // No further methods may be called on this class after calling this method
+  // because the buffer of `buffer_` is now owned by the detached buffer.
   flatbuffers::DetachedBuffer FinishAndTakeFlatBuffer(
       base::span<const uint64_t> input_operands,
       base::span<const uint64_t> output_operands);
diff --git a/services/webnn/tflite/op_resolver.cc b/services/webnn/tflite/op_resolver.cc
index bc67c88..aa4be60 100644
--- a/services/webnn/tflite/op_resolver.cc
+++ b/services/webnn/tflite/op_resolver.cc
@@ -68,6 +68,10 @@
              ::tflite::ops::builtin::Register_FULLY_CONNECTED(),
              /* min_version = */ 1,
              /* max_version = */ 9);
+  AddBuiltin(::tflite::BuiltinOperator_GATHER,
+             ::tflite::ops::builtin::Register_GATHER(),
+             /* min_version = */ 1,
+             /* max_version = */ 3);
   AddBuiltin(::tflite::BuiltinOperator_GREATER,
              ::tflite::ops::builtin::Register_GREATER(),
              /* min_version = */ 1,
diff --git a/services/webnn/webnn_switches.h b/services/webnn/webnn_switches.h
new file mode 100644
index 0000000..1f825f9
--- /dev/null
+++ b/services/webnn/webnn_switches.h
@@ -0,0 +1,22 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WEBNN_WEBNN_SWITCHES_H_
+#define SERVICES_WEBNN_WEBNN_SWITCHES_H_
+
+#include "build/build_config.h"
+
+namespace switches {
+
+#if BUILDFLAG(IS_MAC)
+// Copy the generated Core ML model to the folder specified
+// by --webnn-coreml-dump-model, note: folder needs to be accessible from
+// the GPU sandbox or use --no-sandbox.
+// Usage: --no-sandbox --webnn-coreml-dump-model=/tmp/CoreMLModels
+inline constexpr char kWebNNCoreMlDumpModel[] = "webnn-coreml-dump-model";
+#endif  // BUILDFLAG(IS_MAC)
+
+}  // namespace switches
+
+#endif  // SERVICES_WEBNN_WEBNN_SWITCHES_H_
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index b5bd200..f02204e 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -12020,8 +12020,7 @@
         "args": [
           "--no-wpt-internal",
           "--avd-config=../../tools/android/avd/proto/android_28_google_apis_x86.textpb",
-          "--use-upstream-wpt",
-          "--timeout-multiplier=4"
+          "--use-upstream-wpt"
         ],
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index da0ecbc4..fea2751 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -1213,7 +1213,7 @@
       {
         "args": [
           "tast.setup.FieldTrialConfig=enable",
-          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.key",
+          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.(key|key_type|server)",
           "shard_method=hash"
         ],
         "autotest_name": "tast.chrome-from-gcs",
@@ -1234,7 +1234,7 @@
       {
         "args": [
           "tast.setup.FieldTrialConfig=enable",
-          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.key",
+          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.(key|key_type|server)",
           "shard_method=hash"
         ],
         "autotest_name": "tast.chrome-from-gcs",
@@ -1257,7 +1257,7 @@
       {
         "args": [
           "tast.setup.FieldTrialConfig=enable",
-          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.key",
+          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.(key|key_type|server)",
           "shard_method=hash"
         ],
         "autotest_name": "tast.chrome-from-gcs",
@@ -1299,7 +1299,7 @@
       {
         "args": [
           "tast.setup.FieldTrialConfig=enable",
-          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.key",
+          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.(key|key_type|server)",
           "shard_method=hash"
         ],
         "autotest_name": "tast.chrome-from-gcs",
@@ -1320,7 +1320,7 @@
       {
         "args": [
           "tast.setup.FieldTrialConfig=enable",
-          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.key",
+          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.(key|key_type|server)",
           "shard_method=hash"
         ],
         "autotest_name": "tast.chrome-from-gcs",
@@ -1343,7 +1343,7 @@
       {
         "args": [
           "tast.setup.FieldTrialConfig=enable",
-          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.key",
+          "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.(key|key_type|server)",
           "shard_method=hash"
         ],
         "autotest_name": "tast.chrome-from-gcs",
@@ -5454,9 +5454,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5466,8 +5466,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -5484,9 +5484,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5496,8 +5496,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
@@ -5610,9 +5610,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5622,8 +5622,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -5640,9 +5640,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5652,8 +5652,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index 4b0436d..8c6a9c4 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -19686,9 +19686,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19698,8 +19698,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -19715,9 +19715,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19727,8 +19727,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
@@ -19836,9 +19836,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19848,8 +19848,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -19865,9 +19865,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19877,8 +19877,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index c5ecad1..9cc35d3a 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -41609,9 +41609,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41620,8 +41620,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -41638,9 +41638,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41649,8 +41649,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
@@ -41759,9 +41759,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41770,8 +41770,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -41788,9 +41788,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41799,8 +41799,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
@@ -43107,9 +43107,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43119,8 +43119,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -43137,9 +43137,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43149,8 +43149,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
@@ -43263,9 +43263,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43275,8 +43275,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -43293,9 +43293,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43305,8 +43305,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
@@ -44589,9 +44589,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44600,8 +44600,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -44618,9 +44618,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44629,8 +44629,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
@@ -44739,9 +44739,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44750,8 +44750,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -44768,9 +44768,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44779,8 +44779,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index 45e24aee..a3cbcab 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -4365,6 +4365,9 @@
         "test_id_prefix": "ninja://ui/latency:latency_unittests/"
       },
       {
+        "args": [
+          "--test-launcher-timeout=90000"
+        ],
         "ci_only": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index dd87813..b952614 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -15732,12 +15732,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15747,8 +15747,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -15765,12 +15765,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15780,8 +15780,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
@@ -15908,12 +15908,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 124.0.6367.24",
+        "description": "Run with ash-chrome version 124.0.6367.34",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15923,8 +15923,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6367.24",
-              "revision": "version:124.0.6367.24"
+              "location": "lacros_version_skew_tests_v124.0.6367.34",
+              "revision": "version:124.0.6367.34"
             }
           ],
           "dimensions": {
@@ -15941,12 +15941,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 125.0.6410.0",
+        "description": "Run with ash-chrome version 125.0.6411.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15956,8 +15956,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6410.0",
-              "revision": "version:125.0.6410.0"
+              "location": "lacros_version_skew_tests_v125.0.6411.0",
+              "revision": "version:125.0.6411.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.win.json b/testing/buildbot/chromium.win.json
index c8423a4d..3741998 100644
--- a/testing/buildbot/chromium.win.json
+++ b/testing/buildbot/chromium.win.json
@@ -2967,6 +2967,9 @@
         "test_id_prefix": "ninja://ui/latency:latency_unittests/"
       },
       {
+        "args": [
+          "--test-launcher-timeout=90000"
+        ],
         "ci_only": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6547,6 +6550,9 @@
         "test_id_prefix": "ninja://ui/latency:latency_unittests/"
       },
       {
+        "args": [
+          "--test-launcher-timeout=90000"
+        ],
         "ci_only": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/filters/pixel_tests.filter b/testing/buildbot/filters/pixel_tests.filter
index 6f4c2f47..8b0658d 100644
--- a/testing/buildbot/filters/pixel_tests.filter
+++ b/testing/buildbot/filters/pixel_tests.filter
@@ -85,6 +85,7 @@
 *PopupViewViewsBrowsertest*
 PrivacySandboxDialogViewBrowserTest.*
 PrivacySandboxNoticeBubbleBrowserTest.*
+PrivacySandboxSettingsTopicsInteractiveTest.*
 ProfileMenuViewPixelTest.*
 ProfilePickerUIPixelTest.*
 ProfileTypeChoiceUIPixelTest.*
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index daf6d5f..e7685cd 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -1256,6 +1256,10 @@
     "label": "//components/optimization_guide/internal:ondevice_model_example",
     "type": "additional_compile_target",
   },
+  "ondevice_quality_tests": {
+    "label": "//components/optimization_guide/internal/testing:ondevice_quality_tests",
+    "type": "generated_script",
+  },
   "ondevice_stability_tests": {
     "label": "//components/optimization_guide/internal/testing:ondevice_stability_tests",
     "type": "generated_script",
diff --git a/testing/buildbot/internal.optimization_guide.json b/testing/buildbot/internal.optimization_guide.json
index df615f0..7c00de56 100644
--- a/testing/buildbot/internal.optimization_guide.json
+++ b/testing/buildbot/internal.optimization_guide.json
@@ -274,6 +274,62 @@
           "--binary",
           "chrome"
         ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_quality_tests Intel UHD 630",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "gpu": "8086:9bc5",
+            "os": "Ubuntu-22.04",
+            "pool": "chrome.tests.intelligence"
+          },
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ondevice_quality_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_quality_tests/",
+        "variant_id": "Intel UHD 630"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver",
+          "--binary",
+          "chrome"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_quality_tests NVIDIA GeForce GTX 1660",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "gpu": "10de:2184",
+            "os": "Ubuntu-22.04",
+            "pool": "chrome.tests.intelligence"
+          },
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ondevice_quality_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_quality_tests/",
+        "variant_id": "NVIDIA GeForce GTX 1660"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver",
+          "--binary",
+          "chrome"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
@@ -476,6 +532,33 @@
           "--binary",
           "Google Chrome.app/Contents/MacOS/Google Chrome"
         ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_quality_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "cpu": "arm64",
+            "os": "Mac-13",
+            "pool": "chrome.tests"
+          },
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ondevice_quality_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_quality_tests/"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver",
+          "--binary",
+          "Google Chrome.app/Contents/MacOS/Google Chrome"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
@@ -650,6 +733,33 @@
           "--binary",
           "Google Chrome.app/Contents/MacOS/Google Chrome"
         ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_quality_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Mac-13",
+            "pool": "chrome.tests"
+          },
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ondevice_quality_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_quality_tests/"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver",
+          "--binary",
+          "Google Chrome.app/Contents/MacOS/Google Chrome"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
@@ -831,6 +941,34 @@
           "--binary",
           "Chrome.exe"
         ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_quality_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "cpu": "arm64",
+            "os": "Windows-11",
+            "pool": "chrome.tests",
+            "screen_scaling_percent": "100"
+          },
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ondevice_quality_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_quality_tests/"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver.exe",
+          "--binary",
+          "Chrome.exe"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
@@ -1036,6 +1174,90 @@
           "--chromedriver",
           "chromedriver.exe",
           "--binary",
+          "Chrome.exe"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_quality_tests AMD Radeon RX 5500 XT",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "gpu": "1002:7340",
+            "os": "Windows-10-19045",
+            "pool": "chrome.tests.intelligence"
+          },
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ondevice_quality_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_quality_tests/",
+        "variant_id": "AMD Radeon RX 5500 XT"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver.exe",
+          "--binary",
+          "Chrome.exe"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_quality_tests Intel UHD 630",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "gpu": "8086:9bc5",
+            "os": "Windows-10-19045",
+            "pool": "chrome.tests.intelligence"
+          },
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ondevice_quality_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_quality_tests/",
+        "variant_id": "Intel UHD 630"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver.exe",
+          "--binary",
+          "Chrome.exe"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_quality_tests NVIDIA GeForce GTX 1660",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "gpu": "10de:2184",
+            "os": "Windows-10-19045",
+            "pool": "chrome.tests.intelligence"
+          },
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ondevice_quality_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_quality_tests/",
+        "variant_id": "NVIDIA GeForce GTX 1660"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver.exe",
+          "--binary",
           "Chrome.exe",
           "--target-platform=win32"
         ],
@@ -1301,6 +1523,90 @@
           "--binary",
           "Chrome.exe"
         ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_quality_tests AMD Radeon RX 5500 XT",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "gpu": "1002:7340",
+            "os": "Windows-10-19045",
+            "pool": "chrome.tests.intelligence"
+          },
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ondevice_quality_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_quality_tests/",
+        "variant_id": "AMD Radeon RX 5500 XT"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver.exe",
+          "--binary",
+          "Chrome.exe"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_quality_tests Intel UHD 630",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "gpu": "8086:9bc5",
+            "os": "Windows-10-19045",
+            "pool": "chrome.tests.intelligence"
+          },
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ondevice_quality_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_quality_tests/",
+        "variant_id": "Intel UHD 630"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver.exe",
+          "--binary",
+          "Chrome.exe"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_quality_tests NVIDIA GeForce GTX 1660",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "gpu": "10de:2184",
+            "os": "Windows-10-19045",
+            "pool": "chrome.tests.intelligence"
+          },
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ondevice_quality_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_quality_tests/",
+        "variant_id": "NVIDIA GeForce GTX 1660"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver.exe",
+          "--binary",
+          "Chrome.exe"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index a43d80e0..6c2074c 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -376,7 +376,7 @@
     },
   },
   'chromeos-tast-public-builder': {
-    'args': ["tast.setup.FieldTrialConfig=enable", "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.key", "shard_method=hash"],
+    'args': ["tast.setup.FieldTrialConfig=enable", "maybemissingvars=ui\\.(gaiaPoolDefault|signinProfileTestExtensionManifestKey)|uidetection\\.(key|key_type|server)", "shard_method=hash"],
   },
   'chromium-tester-dev-service-account': {
     'swarming': {
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 4a3b9f4..804c4cbf 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -3334,6 +3334,23 @@
     },
   },
   'leveldb_unittests': {
+    'modifications': {
+      'Linux Tests (dbg)(1)': {
+        'args': [
+          '--test-launcher-timeout=90000',
+        ],
+      },
+      'Win10 Tests x64 (dbg)': {
+        'args': [
+          '--test-launcher-timeout=90000',
+        ],
+      },
+      'win11-arm64-dbg-tests': {
+        'args': [
+          '--test-launcher-timeout=90000',
+        ],
+      },
+    },
     'remove_from': [
       # TODO(https://crbug.com/1432753): Runs too slowly in this configuration.
       'android-oreo-x86-rel',
@@ -4222,7 +4239,6 @@
       'android-webview-pie-x86-wpt-fyi-rel': {
         'args': [
           '--use-upstream-wpt',
-          '--timeout-multiplier=4',
         ],
       },
     },
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index b237c74..9a0f7ec 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -4830,6 +4830,33 @@
       },
     },
 
+    'ondevice_quality_tests_suite': {
+      'ondevice_quality_tests': {
+        'mixins': [
+          'has_native_resultdb_integration',
+        ],
+        'linux_args': [
+          '--chromedriver',
+          'chromedriver',
+          '--binary',
+          'chrome',
+        ],
+        'mac_args': [
+          '--chromedriver',
+          'chromedriver',
+          '--binary',
+          'Google Chrome.app/Contents/MacOS/Google Chrome',
+        ],
+        'win_args': [
+          '--chromedriver',
+          'chromedriver.exe',
+          '--binary',
+          'Chrome.exe',
+        ],
+        'experiment_percentage': 100,
+      },
+    },
+
     'ondevice_stability_tests_suite': {
       'ondevice_stability_tests': {
         'mixins': [
@@ -8070,6 +8097,7 @@
           'MODEL_VALIDATION_TRUNK',
         ],
       },
+      'ondevice_quality_tests_suite': {},
       'ondevice_stability_tests_suite': {},
     },
 
@@ -8097,6 +8125,12 @@
           'MODEL_VALIDATION_TRUNK',
         ],
       },
+      'ondevice_quality_tests_suite': {
+        'variants': [
+          'INTEL_UHD_630',
+          'NVIDIA_GEFORCE_GTX_1660',
+        ],
+      },
       'ondevice_stability_tests_suite': {
         'variants': [
           'INTEL_UHD_630',
@@ -8130,6 +8164,13 @@
           'MODEL_VALIDATION_TRUNK',
         ],
       },
+      'ondevice_quality_tests_suite': {
+        'variants': [
+          'AMD_RADEON_RX_5500_XT',
+          'INTEL_UHD_630',
+          'NVIDIA_GEFORCE_GTX_1660',
+        ],
+      },
       'ondevice_stability_tests_suite': {
         'variants': [
           'AMD_RADEON_RX_5500_XT',
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 8d3e77c7..d457a9a 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -251,32 +251,32 @@
   },
   'LACROS_VERSION_SKEW_BETA': {
     'identifier': 'Lacros version skew testing ash beta',
-    'description': 'Run with ash-chrome version 124.0.6367.24',
+    'description': 'Run with ash-chrome version 124.0.6367.34',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.24/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6367.34/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v124.0.6367.24',
-          'revision': 'version:124.0.6367.24',
+          'location': 'lacros_version_skew_tests_v124.0.6367.34',
+          'revision': 'version:124.0.6367.34',
         },
       ],
     },
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 125.0.6410.0',
+    'description': 'Run with ash-chrome version 125.0.6411.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6410.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v125.0.6410.0',
-          'revision': 'version:125.0.6410.0',
+          'location': 'lacros_version_skew_tests_v125.0.6411.0',
+          'revision': 'version:125.0.6411.0',
         },
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index b1563edf..db4063f4 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3400,6 +3400,24 @@
             ]
         }
     ],
+    "ChromeHomeFrequency": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_4H",
+                    "params": {
+                        "start_surface_return_time_on_tablet_seconds": "14400"
+                    },
+                    "enable_features": [
+                        "StartSurfaceReturnTime"
+                    ]
+                }
+            ]
+        }
+    ],
     "ChromeLabs": [
         {
             "platforms": [
@@ -6599,6 +6617,21 @@
             ]
         }
     ],
+    "DynamicCrxDownloaderPriority": [
+        {
+            "platforms": [
+                "mac"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "DynamicCrxDownloaderPriority"
+                    ]
+                }
+            ]
+        }
+    ],
     "DynamicScrollCullRectExpansion": [
         {
             "platforms": [
@@ -7834,6 +7867,25 @@
             ]
         }
     ],
+    "FixDataPipeTrapBug": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Disabled",
+                    "disable_features": [
+                        "FixDataPipeTrapBug"
+                    ]
+                }
+            ]
+        }
+    ],
     "FixInputQueueingBug": [
         {
             "platforms": [
@@ -8468,24 +8520,6 @@
             ]
         }
     ],
-    "GridTabSwitcherAndroidAnimations": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "params": {
-                        "animation_start_timeout_ms": "300"
-                    },
-                    "enable_features": [
-                        "GridTabSwitcherAndroidAnimations"
-                    ]
-                }
-            ]
-        }
-    ],
     "GwpAsanLinux": [
         {
             "platforms": [
@@ -13849,6 +13883,25 @@
             ]
         }
     ],
+    "PdfOutOfProcessIframe": [
+        {
+            "platforms": [
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "PdfOopif"
+                    ]
+                }
+            ]
+        }
+    ],
     "PdfUseSkiaRenderer": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index 9263345..e41286e 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 926334570f2c209b30fd74cc6c4c34e24b9b8e41
+Subproject commit e41286e1092c950b64a6e6c6a46eb7c716d8a3a9
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index e7ee98d..78332f7 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -2161,7 +2161,7 @@
 
 BASE_FEATURE(kSharedStorageAPIEnableWALForDatabase,
              "SharedStorageAPIEnableWALForDatabase",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kSimulateClickOnAXFocus,
              "SimulateClickOnAXFocus",
diff --git a/third_party/blink/public/blink_resources.grd b/third_party/blink/public/blink_resources.grd
index 8b3cf8a..5541daad 100644
--- a/third_party/blink/public/blink_resources.grd
+++ b/third_party/blink/public/blink_resources.grd
@@ -24,6 +24,8 @@
       <include name="IDR_UASTYLE_THEME_FORCED_COLORS_CSS" file="../renderer/core/html/resources/forced_colors.css" type="BINDATA" compress="brotli"/>
       <include name="IDR_UASTYLE_SELECTLIST_CSS" file="../renderer/core/html/resources/selectlist.css" flattenhtml="true" type="BINDATA" compress="brotli"/>
       <include name="IDR_UASTYLE_STYLABLE_SELECT_CSS" file="../renderer/core/html/resources/stylable_select.css" type="BINDATA" compress="brotli"/>
+      <include name="IDR_UASTYLE_STYLABLE_SELECT_LINUX_CSS" file="../renderer/core/html/resources/stylable_select_linux.css" type="BINDATA" compress="brotli"/>
+      <include name="IDR_UASTYLE_STYLABLE_SELECT_FORCED_COLORS_CSS" file="../renderer/core/html/resources/stylable_select_forced_colors.css" type="BINDATA" compress="brotli"/>
       <include name="IDR_UASTYLE_SVG_CSS" file="../renderer/core/css/svg.css" type="BINDATA" compress="brotli"/>
       <include name="IDR_UASTYLE_MARKER_CSS" file="../renderer/core/css/marker.css" type="BINDATA" compress="brotli"/>
       <include name="IDR_UASTYLE_MATHML_CSS" file="../renderer/core/css/mathml.css" type="BINDATA" compress="brotli"/>
diff --git a/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl b/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl
index e4ce8cc..aed1816 100644
--- a/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl
+++ b/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl
@@ -37,7 +37,6 @@
 {% if property.anchor_mode %}
   AnchorScope anchor_scope(
       AnchorScope::Mode::{{property.anchor_mode.to_enum_value()}},
-      state.StyleBuilder(),
       state.CssToLengthConversionData().GetAnchorEvaluator());
 {% endif %}
 {{(caller(property) ~ '}')}}
diff --git a/third_party/blink/renderer/build/scripts/core/css/templates/css_property_names.h.tmpl b/third_party/blink/renderer/build/scripts/core/css/templates/css_property_names.h.tmpl
index a0b544c..8e78aa9 100644
--- a/third_party/blink/renderer/build/scripts/core/css/templates/css_property_names.h.tmpl
+++ b/third_party/blink/renderer/build/scripts/core/css/templates/css_property_names.h.tmpl
@@ -72,7 +72,7 @@
 static_assert(CSSPropertyID::kColorScheme == kFirstHighPriorityCSSProperty);
 static_assert(CSSPropertyID::kZoom == kLastHighPriorityCSSProperty);
 static_assert((static_cast<int>(kLastHighPriorityCSSProperty) -
-               static_cast<int>(kFirstHighPriorityCSSProperty)) == 38,
+               static_cast<int>(kFirstHighPriorityCSSProperty)) == 39,
               "There should a low number of high-priority properties");
 
 inline int GetCSSPropertyIDIndex(CSSPropertyID id) {
diff --git a/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py b/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
index 45eef937..026ac58 100755
--- a/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
+++ b/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
@@ -60,7 +60,6 @@
     'TextDecorationThickness',
     'StyleAspectRatio',
     'StyleIntrinsicLength',
-    'std::optional<StyleScrollbarColor>',
     'std::optional<StyleOverflowClipMargin>',
     'std::optional<blink::InsetAreaOffsets>',
     'std::optional<PhysicalOffset>',
diff --git a/third_party/blink/renderer/core/animation/color_property_functions.cc b/third_party/blink/renderer/core/animation/color_property_functions.cc
index 064fba0..ca153e7 100644
--- a/third_party/blink/renderer/core/animation/color_property_functions.cc
+++ b/third_party/blink/renderer/core/animation/color_property_functions.cc
@@ -8,124 +8,125 @@
 
 namespace blink {
 
-std::optional<StyleColor> ColorPropertyFunctions::GetInitialColor(
+OptionalStyleColor ColorPropertyFunctions::GetInitialColor(
     const CSSProperty& property,
     const ComputedStyle& initial_style) {
   return GetUnvisitedColor(property, initial_style);
 }
 
 template <typename ComputedStyleOrBuilder>
-std::optional<StyleColor> ColorPropertyFunctions::GetUnvisitedColor(
+OptionalStyleColor ColorPropertyFunctions::GetUnvisitedColor(
     const CSSProperty& property,
     const ComputedStyleOrBuilder& style) {
   switch (property.PropertyID()) {
     case CSSPropertyID::kAccentColor:
       if (style.AccentColor().IsAutoColor())
-        return std::nullopt;
-      return style.AccentColor().ToStyleColor();
+        return OptionalStyleColor();
+      return OptionalStyleColor(style.AccentColor().ToStyleColor());
     case CSSPropertyID::kBackgroundColor:
-      return style.BackgroundColor();
+      return OptionalStyleColor(style.BackgroundColor());
     case CSSPropertyID::kBorderLeftColor:
-      return style.BorderLeftColor();
+      return OptionalStyleColor(style.BorderLeftColor());
     case CSSPropertyID::kBorderRightColor:
-      return style.BorderRightColor();
+      return OptionalStyleColor(style.BorderRightColor());
     case CSSPropertyID::kBorderTopColor:
-      return style.BorderTopColor();
+      return OptionalStyleColor(style.BorderTopColor());
     case CSSPropertyID::kBorderBottomColor:
-      return style.BorderBottomColor();
+      return OptionalStyleColor(style.BorderBottomColor());
     case CSSPropertyID::kCaretColor:
       if (style.CaretColor().IsAutoColor())
-        return std::nullopt;
-      return style.CaretColor().ToStyleColor();
+        return OptionalStyleColor();
+      return OptionalStyleColor(style.CaretColor().ToStyleColor());
     case CSSPropertyID::kColor:
-      return style.Color();
+      return OptionalStyleColor(style.Color());
     case CSSPropertyID::kOutlineColor:
-      return style.OutlineColor();
+      return OptionalStyleColor(style.OutlineColor());
     case CSSPropertyID::kColumnRuleColor:
-      return style.ColumnRuleColor();
+      return OptionalStyleColor(style.ColumnRuleColor());
     case CSSPropertyID::kTextEmphasisColor:
-      return style.TextEmphasisColor();
+      return OptionalStyleColor(style.TextEmphasisColor());
     case CSSPropertyID::kWebkitTextFillColor:
-      return style.TextFillColor();
+      return OptionalStyleColor(style.TextFillColor());
     case CSSPropertyID::kWebkitTextStrokeColor:
-      return style.TextStrokeColor();
+      return OptionalStyleColor(style.TextStrokeColor());
     case CSSPropertyID::kFloodColor:
-      return style.FloodColor();
+      return OptionalStyleColor(style.FloodColor());
     case CSSPropertyID::kLightingColor:
-      return style.LightingColor();
+      return OptionalStyleColor(style.LightingColor());
     case CSSPropertyID::kStopColor:
-      return style.StopColor();
+      return OptionalStyleColor(style.StopColor());
     case CSSPropertyID::kWebkitTapHighlightColor:
-      return style.TapHighlightColor();
+      return OptionalStyleColor(style.TapHighlightColor());
     case CSSPropertyID::kTextDecorationColor:
-      return style.TextDecorationColor();
+      return OptionalStyleColor(style.TextDecorationColor());
     default:
       NOTREACHED();
-      return std::nullopt;
+      return OptionalStyleColor();
   }
 }
 
-template std::optional<StyleColor>
+template OptionalStyleColor
 ColorPropertyFunctions::GetUnvisitedColor<ComputedStyle>(const CSSProperty&,
                                                          const ComputedStyle&);
-template std::optional<StyleColor> ColorPropertyFunctions::GetUnvisitedColor<
+template OptionalStyleColor ColorPropertyFunctions::GetUnvisitedColor<
     ComputedStyleBuilder>(const CSSProperty&, const ComputedStyleBuilder&);
 
 template <typename ComputedStyleOrBuilder>
-std::optional<StyleColor> ColorPropertyFunctions::GetVisitedColor(
+OptionalStyleColor ColorPropertyFunctions::GetVisitedColor(
     const CSSProperty& property,
     const ComputedStyleOrBuilder& style) {
   switch (property.PropertyID()) {
     case CSSPropertyID::kAccentColor:
-      return style.AccentColor();
+      return OptionalStyleColor(style.AccentColor());
     case CSSPropertyID::kBackgroundColor:
-      return style.InternalVisitedBackgroundColor();
+      return OptionalStyleColor(style.InternalVisitedBackgroundColor());
     case CSSPropertyID::kBorderLeftColor:
-      return style.InternalVisitedBorderLeftColor();
+      return OptionalStyleColor(style.InternalVisitedBorderLeftColor());
     case CSSPropertyID::kBorderRightColor:
-      return style.InternalVisitedBorderRightColor();
+      return OptionalStyleColor(style.InternalVisitedBorderRightColor());
     case CSSPropertyID::kBorderTopColor:
-      return style.InternalVisitedBorderTopColor();
+      return OptionalStyleColor(style.InternalVisitedBorderTopColor());
     case CSSPropertyID::kBorderBottomColor:
-      return style.InternalVisitedBorderBottomColor();
+      return OptionalStyleColor(style.InternalVisitedBorderBottomColor());
     case CSSPropertyID::kCaretColor:
       // TODO(rego): "auto" value for caret-color should not interpolate
       // (http://crbug.com/676295).
       if (style.InternalVisitedCaretColor().IsAutoColor())
-        return StyleColor::CurrentColor();
-      return style.InternalVisitedCaretColor().ToStyleColor();
+        return OptionalStyleColor(StyleColor::CurrentColor());
+      return OptionalStyleColor(
+          style.InternalVisitedCaretColor().ToStyleColor());
     case CSSPropertyID::kColor:
-      return style.InternalVisitedColor();
+      return OptionalStyleColor(style.InternalVisitedColor());
     case CSSPropertyID::kOutlineColor:
-      return style.InternalVisitedOutlineColor();
+      return OptionalStyleColor(style.InternalVisitedOutlineColor());
     case CSSPropertyID::kColumnRuleColor:
-      return style.InternalVisitedColumnRuleColor();
+      return OptionalStyleColor(style.InternalVisitedColumnRuleColor());
     case CSSPropertyID::kTextEmphasisColor:
-      return style.InternalVisitedTextEmphasisColor();
+      return OptionalStyleColor(style.InternalVisitedTextEmphasisColor());
     case CSSPropertyID::kWebkitTextFillColor:
-      return style.InternalVisitedTextFillColor();
+      return OptionalStyleColor(style.InternalVisitedTextFillColor());
     case CSSPropertyID::kWebkitTextStrokeColor:
-      return style.InternalVisitedTextStrokeColor();
+      return OptionalStyleColor(style.InternalVisitedTextStrokeColor());
     case CSSPropertyID::kFloodColor:
-      return style.FloodColor();
+      return OptionalStyleColor(style.FloodColor());
     case CSSPropertyID::kLightingColor:
-      return style.LightingColor();
+      return OptionalStyleColor(style.LightingColor());
     case CSSPropertyID::kStopColor:
-      return style.StopColor();
+      return OptionalStyleColor(style.StopColor());
     case CSSPropertyID::kWebkitTapHighlightColor:
-      return style.TapHighlightColor();
+      return OptionalStyleColor(style.TapHighlightColor());
     case CSSPropertyID::kTextDecorationColor:
-      return style.InternalVisitedTextDecorationColor();
+      return OptionalStyleColor(style.InternalVisitedTextDecorationColor());
     default:
       NOTREACHED();
-      return std::nullopt;
+      return OptionalStyleColor();
   }
 }
 
-template std::optional<StyleColor>
+template OptionalStyleColor
 ColorPropertyFunctions::GetVisitedColor<ComputedStyle>(const CSSProperty&,
                                                        const ComputedStyle&);
-template std::optional<StyleColor> ColorPropertyFunctions::GetVisitedColor<
+template OptionalStyleColor ColorPropertyFunctions::GetVisitedColor<
     ComputedStyleBuilder>(const CSSProperty&, const ComputedStyleBuilder&);
 
 void ColorPropertyFunctions::SetUnvisitedColor(const CSSProperty& property,
diff --git a/third_party/blink/renderer/core/animation/color_property_functions.h b/third_party/blink/renderer/core/animation/color_property_functions.h
index a7784dd0..38951087 100644
--- a/third_party/blink/renderer/core/animation/color_property_functions.h
+++ b/third_party/blink/renderer/core/animation/color_property_functions.h
@@ -16,19 +16,42 @@
 class ComputedStyleBuilder;
 class CSSProperty;
 
+class OptionalStyleColor {
+  DISALLOW_NEW();
+
+ public:
+  explicit OptionalStyleColor(const StyleColor& value)
+      : has_value_(true), value_(value) {}
+  OptionalStyleColor() = default;
+
+  bool has_value() const { return has_value_; }
+
+  const StyleColor& value() const {
+    DCHECK(has_value_);
+    return value_;
+  }
+
+  bool operator==(const OptionalStyleColor& other) const {
+    return has_value_ == other.has_value_ && value_ == other.value_;
+  }
+
+  void Trace(Visitor* visitor) const { visitor->Trace(value_); }
+
+ private:
+  bool has_value_ = false;
+  StyleColor value_;
+};
+
 class ColorPropertyFunctions {
  public:
-  static std::optional<StyleColor> GetInitialColor(
-      const CSSProperty&,
-      const ComputedStyle& initial_style);
+  static OptionalStyleColor GetInitialColor(const CSSProperty&,
+                                            const ComputedStyle& initial_style);
   template <typename ComputedStyleOrBuilder>
-  static std::optional<StyleColor> GetUnvisitedColor(
-      const CSSProperty&,
-      const ComputedStyleOrBuilder&);
+  static OptionalStyleColor GetUnvisitedColor(const CSSProperty&,
+                                              const ComputedStyleOrBuilder&);
   template <typename ComputedStyleOrBuilder>
-  static std::optional<StyleColor> GetVisitedColor(
-      const CSSProperty&,
-      const ComputedStyleOrBuilder&);
+  static OptionalStyleColor GetVisitedColor(const CSSProperty&,
+                                            const ComputedStyleOrBuilder&);
   static void SetUnvisitedColor(const CSSProperty&,
                                 ComputedStyleBuilder&,
                                 const Color&);
diff --git a/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc
index f18e3f8..af246d45 100644
--- a/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc
@@ -167,9 +167,14 @@
     : public CSSInterpolationType::CSSConversionChecker {
  public:
   InheritedColorChecker(const CSSProperty& property,
-                        const std::optional<StyleColor>& color)
+                        const OptionalStyleColor& color)
       : property_(property), color_(color) {}
 
+  void Trace(Visitor* visitor) const final {
+    visitor->Trace(color_);
+    CSSInterpolationType::CSSConversionChecker::Trace(visitor);
+  }
+
  private:
   bool IsValid(const StyleResolverState& state,
                const InterpolationValue& underlying) const final {
@@ -178,7 +183,7 @@
   }
 
   const CSSProperty& property_;
-  const std::optional<StyleColor> color_;
+  const OptionalStyleColor color_;
 };
 
 InterpolationValue CSSColorInterpolationType::MaybeConvertNeutral(
@@ -196,9 +201,8 @@
 InterpolationValue CSSColorInterpolationType::MaybeConvertInitial(
     const StyleResolverState& state,
     ConversionCheckers& conversion_checkers) const {
-  std::optional<StyleColor> initial_color =
-      ColorPropertyFunctions::GetInitialColor(
-          CssProperty(), state.GetDocument().GetStyleResolver().InitialStyle());
+  OptionalStyleColor initial_color = ColorPropertyFunctions::GetInitialColor(
+      CssProperty(), state.GetDocument().GetStyleResolver().InitialStyle());
   if (!initial_color.has_value()) {
     return nullptr;
   }
@@ -219,7 +223,7 @@
     return nullptr;
   // Visited color can never explicitly inherit from parent visited color so
   // only use the unvisited color.
-  std::optional<StyleColor> inherited_color =
+  OptionalStyleColor inherited_color =
       ColorPropertyFunctions::GetUnvisitedColor(CssProperty(),
                                                 *state.ParentStyle());
   conversion_checkers.push_back(MakeGarbageCollected<InheritedColorChecker>(
@@ -301,8 +305,8 @@
 }
 
 InterpolationValue CSSColorInterpolationType::ConvertStyleColorPair(
-    const std::optional<StyleColor>& unvisited_color,
-    const std::optional<StyleColor>& visited_color,
+    const OptionalStyleColor& unvisited_color,
+    const OptionalStyleColor& visited_color,
     mojom::blink::ColorScheme color_scheme,
     const ui::ColorProvider* color_provider) {
   if (!unvisited_color.has_value() || !visited_color.has_value()) {
diff --git a/third_party/blink/renderer/core/animation/css_color_interpolation_type.h b/third_party/blink/renderer/core/animation/css_color_interpolation_type.h
index a3cac18c..0798d1f 100644
--- a/third_party/blink/renderer/core/animation/css_color_interpolation_type.h
+++ b/third_party/blink/renderer/core/animation/css_color_interpolation_type.h
@@ -14,6 +14,7 @@
 
 namespace blink {
 
+class OptionalStyleColor;
 class StyleColor;
 
 class CORE_EXPORT CSSColorInterpolationType : public CSSInterpolationType {
@@ -84,8 +85,8 @@
                                        const StyleResolverState*,
                                        ConversionCheckers&) const final;
   static InterpolationValue ConvertStyleColorPair(
-      const std::optional<StyleColor>&,
-      const std::optional<StyleColor>&,
+      const OptionalStyleColor&,
+      const OptionalStyleColor&,
       mojom::blink::ColorScheme color_scheme,
       const ui::ColorProvider* color_provider);
   static InterpolationValue ConvertStyleColorPair(
diff --git a/third_party/blink/renderer/core/animation/css_paint_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_paint_interpolation_type.cc
index e01e32dd..29e5b85e 100644
--- a/third_party/blink/renderer/core/animation/css_paint_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_paint_interpolation_type.cc
@@ -90,6 +90,11 @@
   InheritedPaintChecker(const CSSProperty& property, const StyleColor& color)
       : property_(property), valid_color_(true), color_(color) {}
 
+  void Trace(Visitor* visitor) const final {
+    visitor->Trace(color_);
+    CSSInterpolationType::CSSConversionChecker::Trace(visitor);
+  }
+
  private:
   bool IsValid(const StyleResolverState& state,
                const InterpolationValue& underlying) const final {
diff --git a/third_party/blink/renderer/core/animation/css_scrollbar_color_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_scrollbar_color_interpolation_type.cc
index 246e4517..4ebd856 100644
--- a/third_party/blink/renderer/core/animation/css_scrollbar_color_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_scrollbar_color_interpolation_type.cc
@@ -51,9 +51,9 @@
   ~CSSScrollbarColorNonInterpolableValue() final = default;
 
   static scoped_refptr<CSSScrollbarColorNonInterpolableValue> Create(
-      std::optional<StyleScrollbarColor> scrollbar_color) {
+      const StyleScrollbarColor* scrollbar_color) {
     return base::AdoptRef(
-        new CSSScrollbarColorNonInterpolableValue(scrollbar_color.has_value()));
+        new CSSScrollbarColorNonInterpolableValue(scrollbar_color));
   }
 
   bool HasValue() const { return has_value_; }
@@ -91,16 +91,21 @@
     : public CSSInterpolationType::CSSConversionChecker {
  public:
   explicit InheritedScrollbarColorChecker(
-      std::optional<StyleScrollbarColor> scrollbar_color)
+      const StyleScrollbarColor* scrollbar_color)
       : scrollbar_color_(scrollbar_color) {}
 
+  void Trace(Visitor* visitor) const final {
+    visitor->Trace(scrollbar_color_);
+    CSSInterpolationType::CSSConversionChecker::Trace(visitor);
+  }
+
  private:
   bool IsValid(const StyleResolverState& state,
                const InterpolationValue& underlying) const final {
     return scrollbar_color_ == state.ParentStyle()->ScrollbarColor();
   }
 
-  std::optional<StyleScrollbarColor> scrollbar_color_;
+  Member<const StyleScrollbarColor> scrollbar_color_;
 };
 
 InterpolationValue CSSScrollbarColorInterpolationType::MaybeConvertNeutral(
@@ -113,7 +118,7 @@
 InterpolationValue CSSScrollbarColorInterpolationType::MaybeConvertInitial(
     const StyleResolverState& state,
     ConversionCheckers& conversion_checkers) const {
-  std::optional<StyleScrollbarColor> initial_scrollbar_color =
+  const StyleScrollbarColor* initial_scrollbar_color =
       state.GetDocument().GetStyleResolver().InitialStyle().ScrollbarColor();
   return InterpolationValue(
       CreateScrollbarColorValue(initial_scrollbar_color),
@@ -127,13 +132,13 @@
     return nullptr;
   }
 
-  std::optional<StyleScrollbarColor> inherited_scrollbar_color =
+  const StyleScrollbarColor* inherited_scrollbar_color =
       state.ParentStyle()->ScrollbarColor();
   conversion_checkers.push_back(
       MakeGarbageCollected<InheritedScrollbarColorChecker>(
           inherited_scrollbar_color));
 
-  if (!inherited_scrollbar_color.has_value()) {
+  if (!inherited_scrollbar_color) {
     return nullptr;
   }
 
@@ -167,9 +172,9 @@
 
   StyleScrollbarColor scrollbar_color(thumb_color.value(), track_color.value());
 
-  return InterpolationValue(InterpolableScrollbarColor::Create(scrollbar_color),
-                            CSSScrollbarColorNonInterpolableValue::Create(
-                                std::make_optional(scrollbar_color)));
+  return InterpolationValue(
+      InterpolableScrollbarColor::Create(scrollbar_color),
+      CSSScrollbarColorNonInterpolableValue::Create(&scrollbar_color));
 }
 
 PairwiseInterpolationValue
@@ -219,18 +224,17 @@
     StyleResolverState& state) const {
   const auto& interpolable_scrollbar_color =
       To<InterpolableScrollbarColor>(interpolable_value);
-  StyleScrollbarColor scrollbar_color =
-      interpolable_scrollbar_color.GetScrollbarColor(state);
-  state.StyleBuilder().SetScrollbarColor(scrollbar_color);
+  state.StyleBuilder().SetScrollbarColor(
+      interpolable_scrollbar_color.GetScrollbarColor(state));
 }
 
 InterpolableScrollbarColor*
 CSSScrollbarColorInterpolationType::CreateScrollbarColorValue(
-    std::optional<StyleScrollbarColor> scrollbar_color) const {
-  if (!scrollbar_color.has_value()) {
+    const StyleScrollbarColor* scrollbar_color) const {
+  if (!scrollbar_color) {
     return nullptr;
   }
-  return InterpolableScrollbarColor::Create(scrollbar_color.value());
+  return InterpolableScrollbarColor::Create(*scrollbar_color);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/css_scrollbar_color_interpolation_type.h b/third_party/blink/renderer/core/animation/css_scrollbar_color_interpolation_type.h
index 3c513395..989326e 100644
--- a/third_party/blink/renderer/core/animation/css_scrollbar_color_interpolation_type.h
+++ b/third_party/blink/renderer/core/animation/css_scrollbar_color_interpolation_type.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_CSS_SCROLLBAR_COLOR_INTERPOLATION_TYPE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_CSS_SCROLLBAR_COLOR_INTERPOLATION_TYPE_H_
 
-#include <memory>
 #include "third_party/blink/renderer/core/animation/css_interpolation_type.h"
 #include "third_party/blink/renderer/core/animation/interpolable_scrollbar_color.h"
 #include "third_party/blink/renderer/core/core_export.h"
@@ -38,7 +37,7 @@
 
  private:
   InterpolableScrollbarColor* CreateScrollbarColorValue(
-      std::optional<StyleScrollbarColor>) const;
+      const StyleScrollbarColor*) const;
   InterpolationValue MaybeConvertNeutral(const InterpolationValue& underlying,
                                          ConversionCheckers&) const final;
   InterpolationValue MaybeConvertInitial(const StyleResolverState&,
diff --git a/third_party/blink/renderer/core/animation/css_shadow_list_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_shadow_list_interpolation_type.cc
index 6a68a15..209af16 100644
--- a/third_party/blink/renderer/core/animation/css_shadow_list_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_shadow_list_interpolation_type.cc
@@ -72,8 +72,13 @@
     : public CSSInterpolationType::CSSConversionChecker {
  public:
   InheritedShadowListChecker(const CSSProperty& property,
-                             scoped_refptr<const ShadowList> shadow_list)
-      : property_(property), shadow_list_(std::move(shadow_list)) {}
+                             const ShadowList* shadow_list)
+      : property_(property), shadow_list_(shadow_list) {}
+
+  void Trace(Visitor* visitor) const final {
+    visitor->Trace(shadow_list_);
+    CSSInterpolationType::CSSConversionChecker::Trace(visitor);
+  }
 
  private:
   bool IsValid(const StyleResolverState& state,
@@ -88,7 +93,7 @@
   }
 
   const CSSProperty& property_;
-  scoped_refptr<const ShadowList> shadow_list_;
+  Member<const ShadowList> shadow_list_;
 };
 
 InterpolationValue CSSShadowListInterpolationType::MaybeConvertInherit(
@@ -177,7 +182,7 @@
   underlying_value_owner.Set(*this, value);
 }
 
-static scoped_refptr<ShadowList> CreateShadowList(
+static ShadowList* CreateShadowList(
     const InterpolableValue& interpolable_value,
     const NonInterpolableValue* non_interpolable_value,
     const StyleResolverState& state) {
@@ -186,25 +191,26 @@
   if (length == 0)
     return nullptr;
   ShadowDataVector shadows;
+  shadows.ReserveInitialCapacity(length);
   for (wtf_size_t i = 0; i < length; i++) {
     shadows.push_back(To<InterpolableShadow>(interpolable_list.Get(i))
                           ->CreateShadowData(state));
   }
-  return ShadowList::Adopt(shadows);
+  return MakeGarbageCollected<ShadowList>(std::move(shadows));
 }
 
 void CSSShadowListInterpolationType::ApplyStandardPropertyValue(
     const InterpolableValue& interpolable_value,
     const NonInterpolableValue* non_interpolable_value,
     StyleResolverState& state) const {
-  scoped_refptr<ShadowList> shadow_list =
+  ShadowList* shadow_list =
       CreateShadowList(interpolable_value, non_interpolable_value, state);
   switch (CssProperty().PropertyID()) {
     case CSSPropertyID::kBoxShadow:
-      state.StyleBuilder().SetBoxShadow(std::move(shadow_list));
+      state.StyleBuilder().SetBoxShadow(shadow_list);
       return;
     case CSSPropertyID::kTextShadow:
-      state.StyleBuilder().SetTextShadow(std::move(shadow_list));
+      state.StyleBuilder().SetTextShadow(shadow_list);
       return;
     default:
       NOTREACHED();
diff --git a/third_party/blink/renderer/core/animation/interpolable_scrollbar_color.cc b/third_party/blink/renderer/core/animation/interpolable_scrollbar_color.cc
index 5d33733..5ff8a8a1 100644
--- a/third_party/blink/renderer/core/animation/interpolable_scrollbar_color.cc
+++ b/third_party/blink/renderer/core/animation/interpolable_scrollbar_color.cc
@@ -46,9 +46,9 @@
       thumb_color_->CloneAndZero(), track_color_->CloneAndZero());
 }
 
-StyleScrollbarColor InterpolableScrollbarColor::GetScrollbarColor(
+StyleScrollbarColor* InterpolableScrollbarColor::GetScrollbarColor(
     const StyleResolverState& state) const {
-  return StyleScrollbarColor(
+  return MakeGarbageCollected<StyleScrollbarColor>(
       StyleColor(CSSColorInterpolationType::ResolveInterpolableColor(
           *thumb_color_, state)),
       StyleColor(CSSColorInterpolationType::ResolveInterpolableColor(
diff --git a/third_party/blink/renderer/core/animation/interpolable_scrollbar_color.h b/third_party/blink/renderer/core/animation/interpolable_scrollbar_color.h
index 7b854e8..a9e9066 100644
--- a/third_party/blink/renderer/core/animation/interpolable_scrollbar_color.h
+++ b/third_party/blink/renderer/core/animation/interpolable_scrollbar_color.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_INTERPOLABLE_SCROLLBAR_COLOR_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_INTERPOLABLE_SCROLLBAR_COLOR_H_
 
-#include <memory>
 #include "base/notreached.h"
 #include "third_party/blink/renderer/core/animation/interpolable_color.h"
 #include "third_party/blink/renderer/core/animation/interpolable_value.h"
@@ -26,7 +25,7 @@
   static InterpolableScrollbarColor* Create(StyleScrollbarColor);
   bool IsScrollbarColor() const final { return true; }
 
-  StyleScrollbarColor GetScrollbarColor(const StyleResolverState&) const;
+  StyleScrollbarColor* GetScrollbarColor(const StyleResolverState&) const;
 
   void Scale(double scale) final;
   void Add(const InterpolableValue& other) final;
diff --git a/third_party/blink/renderer/core/animation/interpolable_style_color.h b/third_party/blink/renderer/core/animation/interpolable_style_color.h
index 7044b15..cf5632a7 100644
--- a/third_party/blink/renderer/core/animation/interpolable_style_color.h
+++ b/third_party/blink/renderer/core/animation/interpolable_style_color.h
@@ -44,6 +44,7 @@
 
   void Trace(Visitor* v) const override {
     BaseInterpolableColor::Trace(v);
+    v->Trace(style_color_);
     v->Trace(from_color_);
     v->Trace(to_color_);
   }
diff --git a/third_party/blink/renderer/core/css/anchor_evaluator.cc b/third_party/blink/renderer/core/css/anchor_evaluator.cc
deleted file mode 100644
index 7455634..0000000
--- a/third_party/blink/renderer/core/css/anchor_evaluator.cc
+++ /dev/null
@@ -1,50 +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 "third_party/blink/renderer/core/css/anchor_evaluator.h"
-
-#include "third_party/blink/renderer/core/style/computed_style.h"
-#include "third_party/blink/renderer/core/style/scoped_css_name.h"
-
-namespace blink {
-
-void AnchorEvaluator::Trace(Visitor* visitor) const {
-  visitor->Trace(position_anchor_name_);
-}
-
-AnchorScope::AnchorScope(Mode mode,
-                         const ComputedStyleBuilder& builder,
-                         AnchorEvaluator* anchor_evaluator)
-    : AnchorScope(mode,
-                  builder.InsetAreaOffsets(),
-                  builder.PositionAnchor(),
-                  anchor_evaluator) {}
-
-AnchorScope::AnchorScope(Mode mode,
-                         std::optional<InsetAreaOffsets> inset_area_offsets,
-                         const ScopedCSSName* position_anchor_name,
-                         AnchorEvaluator* anchor_evaluator)
-    : anchor_evaluator_(anchor_evaluator) {
-  if (anchor_evaluator) {
-    original_mode_ = anchor_evaluator_->mode_;
-    original_inset_area_offsets_ = anchor_evaluator_->inset_area_offsets_;
-    original_position_anchor_name_ = anchor_evaluator_->position_anchor_name_;
-    anchor_evaluator_->mode_ = mode;
-    anchor_evaluator_->inset_area_offsets_ = inset_area_offsets;
-    anchor_evaluator_->position_anchor_name_ = position_anchor_name;
-  }
-}
-
-AnchorScope::AnchorScope(Mode mode, AnchorEvaluator* anchor_evaluator)
-    : anchor_evaluator_(anchor_evaluator) {
-  if (anchor_evaluator) {
-    original_mode_ = anchor_evaluator->mode_;
-    anchor_evaluator->mode_ = mode;
-    // Store the existing value so that we reset back to the same value
-    original_inset_area_offsets_ = anchor_evaluator->inset_area_offsets_;
-    original_position_anchor_name_ = anchor_evaluator->position_anchor_name_;
-  }
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/anchor_evaluator.h b/third_party/blink/renderer/core/css/anchor_evaluator.h
index 4e8df88..ab89b3b 100644
--- a/third_party/blink/renderer/core/css/anchor_evaluator.h
+++ b/third_party/blink/renderer/core/css/anchor_evaluator.h
@@ -90,7 +90,10 @@
   // Evaluates an anchor() or anchor-size() query.
   // Returns |nullopt| if the query is invalid (e.g., no targets or wrong
   // axis.), in which case the fallback should be used.
-  virtual std::optional<LayoutUnit> Evaluate(const AnchorQuery&) = 0;
+  virtual std::optional<LayoutUnit> Evaluate(
+      const AnchorQuery&,
+      const ScopedCSSName* position_anchor,
+      const std::optional<InsetAreaOffsets>&) = 0;
 
   // Take the computed inset-area and position-anchor and compute the physical
   // offsets to inset the containing block with.
@@ -103,34 +106,22 @@
   virtual std::optional<PhysicalOffset> ComputeAnchorCenterOffsets(
       const ComputedStyleBuilder&) = 0;
 
-  virtual void Trace(Visitor*) const;
+  virtual void Trace(Visitor*) const {}
 
  protected:
-  const ScopedCSSName* GetPositionAnchorName() const {
-    return position_anchor_name_;
-  }
   Mode GetMode() const { return mode_; }
-  std::optional<InsetAreaOffsets> GetInsetAreaOffsets() const {
-    return inset_area_offsets_;
-  }
 
  private:
   friend class AnchorScope;
 
   // The computed position-anchor in use for the current try option.
-  Member<const ScopedCSSName> position_anchor_name_;
-  // The computed position-anchor in use for the current try option.
   Mode mode_ = Mode::kNone;
-  // The computed inset-area offsets in use for the current try option.
-  std::optional<InsetAreaOffsets> inset_area_offsets_;
 };
 
-// Temporarily changes Mode, default anchor (position-anchor), and apply the
-// current inset-area to modify the containing block position / size. When going
-// out of scope the AnchorEvaluator is reset back to its previous state with
-// caches that need to be invalidated cleared.
+// Temporarily sets the Mode of an AnchorEvaluator.
 //
-// If the anchor_evaluator is nullptr the AnchorScope should have no effect.
+// This class behaves like base::AutoReset, except it allows `anchor_evalutor`
+// to be nullptr (in which case the AnchorScope has no effect).
 //
 // See AnchorEvaluator::Mode for more information.
 class CORE_EXPORT AnchorScope {
@@ -139,29 +130,22 @@
  public:
   using Mode = AnchorEvaluator::Mode;
 
-  // Temporarily change Mode, applied inset-area and position-anchor.
-  AnchorScope(Mode, const ComputedStyleBuilder&, AnchorEvaluator*);
-  AnchorScope(Mode,
-              std::optional<InsetAreaOffsets>,
-              const ScopedCSSName* position_anchor_name,
-              AnchorEvaluator*);
-  // Temporarily change Mode only. Applied inset-area and position-anchor stay
-  // the same.
-  AnchorScope(Mode, AnchorEvaluator*);
-
+  explicit AnchorScope(Mode mode, AnchorEvaluator* anchor_evaluator)
+      : target_(anchor_evaluator ? &anchor_evaluator->mode_ : nullptr),
+        original_(anchor_evaluator ? anchor_evaluator->mode_ : Mode::kNone) {
+    if (target_) {
+      *target_ = mode;
+    }
+  }
   ~AnchorScope() {
-    if (anchor_evaluator_) {
-      anchor_evaluator_->mode_ = original_mode_;
-      anchor_evaluator_->inset_area_offsets_ = original_inset_area_offsets_;
-      anchor_evaluator_->position_anchor_name_ = original_position_anchor_name_;
+    if (target_) {
+      *target_ = original_;
     }
   }
 
  private:
-  AnchorEvaluator* anchor_evaluator_ = nullptr;
-  const ScopedCSSName* original_position_anchor_name_ = nullptr;
-  Mode original_mode_ = Mode::kNone;
-  std::optional<InsetAreaOffsets> original_inset_area_offsets_;
+  Mode* target_;
+  Mode original_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/anchor_results.cc b/third_party/blink/renderer/core/css/anchor_results.cc
index 1fbf0dd6e..94234e4 100644
--- a/third_party/blink/renderer/core/css/anchor_results.cc
+++ b/third_party/blink/renderer/core/css/anchor_results.cc
@@ -18,7 +18,11 @@
   visitor->Trace(map_);
 }
 
-std::optional<LayoutUnit> AnchorResults::Evaluate(const AnchorQuery& query) {
+std::optional<LayoutUnit> AnchorResults::Evaluate(
+    const AnchorQuery& query,
+    const ScopedCSSName* position_anchor,
+    const std::optional<InsetAreaOffsets>& inset_area_results) {
+  // TODO(crbug.com/333423706): Handle `position_anchor`, `inset_area_results`.
   if (GetMode() == AnchorEvaluator::Mode::kNone) {
     return std::nullopt;
   }
@@ -63,10 +67,11 @@
     Mode mode = key->GetMode();
     std::optional<InsetAreaOffsets> inset_area =
         IsBaseMode(mode) ? std::nullopt : style.InsetAreaOffsets();
-    AnchorScope anchor_scope(mode, inset_area, position_anchor, evaluator);
+    AnchorScope anchor_scope(mode, evaluator);
     std::optional<LayoutUnit> new_result =
-        evaluator ? evaluator->Evaluate(key->Query())
-                  : std::optional<LayoutUnit>();
+        evaluator
+            ? evaluator->Evaluate(key->Query(), position_anchor, inset_area)
+            : std::optional<LayoutUnit>();
     if (new_result != old_result) {
       return true;
     }
diff --git a/third_party/blink/renderer/core/css/anchor_results.h b/third_party/blink/renderer/core/css/anchor_results.h
index 55e892e..be225e4a 100644
--- a/third_party/blink/renderer/core/css/anchor_results.h
+++ b/third_party/blink/renderer/core/css/anchor_results.h
@@ -98,7 +98,10 @@
   DISALLOW_NEW();
 
  public:
-  std::optional<LayoutUnit> Evaluate(const AnchorQuery&) override;
+  std::optional<LayoutUnit> Evaluate(
+      const AnchorQuery&,
+      const ScopedCSSName* position_anchor,
+      const std::optional<InsetAreaOffsets>&) override;
   std::optional<InsetAreaOffsets> ComputeInsetAreaOffsetsForLayout(
       const ScopedCSSName* position_anchor,
       InsetArea inset_area) override;
diff --git a/third_party/blink/renderer/core/css/anchor_results_test.cc b/third_party/blink/renderer/core/css/anchor_results_test.cc
index f38c709..78d0d452 100644
--- a/third_party/blink/renderer/core/css/anchor_results_test.cc
+++ b/third_party/blink/renderer/core/css/anchor_results_test.cc
@@ -271,7 +271,9 @@
   AnchorResults results1;
   {
     AnchorScope anchor_scope(AnchorScope::Mode::kTop, &results1);
-    results1.Evaluate(CreateItem(Options{})->Query());
+    results1.Evaluate(CreateItem(Options{})->Query(),
+                      /* position_anchor */ nullptr,
+                      /* inset_area_offsets */ std::nullopt);
   }
 
   AnchorResults results2;
@@ -292,7 +294,9 @@
   results.Set(mode, item->Query(), LayoutUnit(42.0));
 
   AnchorScope anchor_scope(mode, &results);
-  EXPECT_EQ(LayoutUnit(42.0), results.Evaluate(item->Query()));
+  EXPECT_EQ(LayoutUnit(42.0),
+            results.Evaluate(item->Query(), /* position_anchor */ nullptr,
+                             /* inset_area_offsets */ std::nullopt));
 }
 
 TEST_F(AnchorResultsTest, EvaluateWrongMode) {
@@ -306,7 +310,9 @@
   results.Set(mode, item->Query(), LayoutUnit(42.0));
 
   AnchorScope anchor_scope(AnchorScope::Mode::kTop, &results);
-  EXPECT_EQ(std::nullopt, results.Evaluate(item->Query()));
+  EXPECT_EQ(std::nullopt,
+            results.Evaluate(item->Query(), /* position_anchor */ nullptr,
+                             /* inset_area_offsets */ std::nullopt));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/build.gni b/third_party/blink/renderer/core/css/build.gni
index 83cdf35..3c6831d 100644
--- a/third_party/blink/renderer/core/css/build.gni
+++ b/third_party/blink/renderer/core/css/build.gni
@@ -22,7 +22,6 @@
   "abstract_property_set_css_style_declaration.h",
   "active_style_sheets.cc",
   "active_style_sheets.h",
-  "anchor_evaluator.cc",
   "anchor_evaluator.h",
   "anchor_results.cc",
   "anchor_results.h",
@@ -200,6 +199,8 @@
   "css_length_resolver.h",
   "css_light_dark_value_pair.cc",
   "css_light_dark_value_pair.h",
+  "css_appearance_auto_base_select_value_pair.cc",
+  "css_appearance_auto_base_select_value_pair.h",
   "css_markup.cc",
   "css_markup.h",
   "css_math_expression_node.cc",
diff --git a/third_party/blink/renderer/core/css/css.dict b/third_party/blink/renderer/core/css/css.dict
index fffe1f8..5522291 100644
--- a/third_party/blink/renderer/core/css/css.dict
+++ b/third_party/blink/renderer/core/css/css.dict
@@ -939,6 +939,7 @@
 "color-contrast"
 "accentcolor"
 "accentcolortext"
+"-internal-auto-base-select"
 
 # display-state
 # normal
diff --git a/third_party/blink/renderer/core/css/css_appearance_auto_base_select_value_pair.cc b/third_party/blink/renderer/core/css/css_appearance_auto_base_select_value_pair.cc
new file mode 100644
index 0000000..39b59beb9
--- /dev/null
+++ b/third_party/blink/renderer/core/css/css_appearance_auto_base_select_value_pair.cc
@@ -0,0 +1,15 @@
+// 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 "third_party/blink/renderer/core/css/css_appearance_auto_base_select_value_pair.h"
+
+namespace blink {
+
+String CSSAppearanceAutoBaseSelectValuePair::CustomCSSText() const {
+  String first = First().CssText();
+  String second = Second().CssText();
+  return "-internal-appearance-auto-base-select(" + first + ", " + second + ")";
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_appearance_auto_base_select_value_pair.h b/third_party/blink/renderer/core/css/css_appearance_auto_base_select_value_pair.h
new file mode 100644
index 0000000..de767dbf
--- /dev/null
+++ b/third_party/blink/renderer/core/css/css_appearance_auto_base_select_value_pair.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 THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_AUTO_BASE_SELECT_VALUE_PAIR_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_AUTO_BASE_SELECT_VALUE_PAIR_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/css/css_value_pair.h"
+
+namespace blink {
+
+class CORE_EXPORT CSSAppearanceAutoBaseSelectValuePair : public CSSValuePair {
+ public:
+  CSSAppearanceAutoBaseSelectValuePair(const CSSValue* first,
+                                       const CSSValue* second)
+      : CSSValuePair(kAppearanceAutoBaseSelectValuePairClass, first, second) {}
+  String CustomCSSText() const;
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
+    CSSValuePair::TraceAfterDispatch(visitor);
+  }
+};
+
+template <>
+struct DowncastTraits<CSSAppearanceAutoBaseSelectValuePair> {
+  static bool AllowFrom(const CSSValue& value) {
+    return value.IsAppearanceAutoBaseSelectValuePair();
+  }
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_AUTO_BASE_SELECT_VALUE_PAIR_H_
diff --git a/third_party/blink/renderer/core/css/css_default_style_sheets.cc b/third_party/blink/renderer/core/css/css_default_style_sheets.cc
index a3339ee..4077ab5b 100644
--- a/third_party/blink/renderer/core/css/css_default_style_sheets.cc
+++ b/third_party/blink/renderer/core/css/css_default_style_sheets.cc
@@ -129,6 +129,7 @@
   fullscreen_style_sheet_.Clear();
   selectlist_style_sheet_.Clear();
   stylable_select_style_sheet_.Clear();
+  stylable_select_forced_colors_style_sheet_.Clear();
   marker_style_sheet_.Clear();
   form_controls_not_vertical_style_sheet_.Clear();
   form_controls_not_vertical_style_text_sheet_.Clear();
@@ -460,10 +461,17 @@
     return false;
   }
 
-  String forced_colors_rules =
-      RuntimeEnabledFeatures::ForcedColorsEnabled()
-          ? UncompressResourceAsASCIIString(IDR_UASTYLE_THEME_FORCED_COLORS_CSS)
-          : String();
+  String forced_colors_rules = String();
+  if (RuntimeEnabledFeatures::ForcedColorsEnabled()) {
+    forced_colors_rules =
+        forced_colors_rules +
+        UncompressResourceAsASCIIString(IDR_UASTYLE_THEME_FORCED_COLORS_CSS);
+    if (RuntimeEnabledFeatures::StylableSelectEnabled()) {
+      forced_colors_rules = forced_colors_rules +
+                            UncompressResourceAsASCIIString(
+                                IDR_UASTYLE_STYLABLE_SELECT_FORCED_COLORS_CSS);
+    }
+  }
   forced_colors_style_sheet_ = ParseUASheet(forced_colors_rules);
 
   if (!default_forced_color_style_) {
@@ -530,6 +538,7 @@
   visitor->Trace(fullscreen_style_sheet_);
   visitor->Trace(selectlist_style_sheet_);
   visitor->Trace(stylable_select_style_sheet_);
+  visitor->Trace(stylable_select_forced_colors_style_sheet_);
   visitor->Trace(marker_style_sheet_);
   visitor->Trace(form_controls_not_vertical_style_sheet_);
   visitor->Trace(form_controls_not_vertical_style_text_sheet_);
diff --git a/third_party/blink/renderer/core/css/css_default_style_sheets.h b/third_party/blink/renderer/core/css/css_default_style_sheets.h
index 0ff68c9..a1a99c4 100644
--- a/third_party/blink/renderer/core/css/css_default_style_sheets.h
+++ b/third_party/blink/renderer/core/css/css_default_style_sheets.h
@@ -87,6 +87,9 @@
   StyleSheetContents* StylableSelectStyleSheet() {
     return stylable_select_style_sheet_.Get();
   }
+  StyleSheetContents* StylableSelectForcedColorsStyleSheet() {
+    return stylable_select_forced_colors_style_sheet_.Get();
+  }
   StyleSheetContents* SvgStyleSheet() { return svg_style_sheet_.Get(); }
   StyleSheetContents* MathmlStyleSheet() { return mathml_style_sheet_.Get(); }
   StyleSheetContents* MediaControlsStyleSheet() {
@@ -167,6 +170,7 @@
   Member<StyleSheetContents> fullscreen_style_sheet_;
   Member<StyleSheetContents> selectlist_style_sheet_;
   Member<StyleSheetContents> stylable_select_style_sheet_;
+  Member<StyleSheetContents> stylable_select_forced_colors_style_sheet_;
   Member<StyleSheetContents> marker_style_sheet_;
   Member<StyleSheetContents> forced_colors_style_sheet_;
   Member<StyleSheetContents> form_controls_not_vertical_style_sheet_;
diff --git a/third_party/blink/renderer/core/css/css_length_resolver.h b/third_party/blink/renderer/core/css/css_length_resolver.h
index b0e0bffd..0416c9e5 100644
--- a/third_party/blink/renderer/core/css/css_length_resolver.h
+++ b/third_party/blink/renderer/core/css/css_length_resolver.h
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/anchor_evaluator.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
+#include "third_party/blink/renderer/core/style/inset_area.h"
 #include "third_party/blink/renderer/core/style/scoped_css_name.h"
 #include "third_party/blink/renderer/platform/geometry/length.h"
 #include "third_party/blink/renderer/platform/text/writing_mode.h"
@@ -67,6 +68,10 @@
 
   // The AnchorEvaluator used to evaluate anchor()/anchor-size() queries.
   virtual AnchorEvaluator* GetAnchorEvaluator() const { return nullptr; }
+  virtual const ScopedCSSName* GetPositionAnchor() const { return nullptr; }
+  virtual std::optional<InsetAreaOffsets> GetInsetAreaOffsets() const {
+    return std::nullopt;
+  }
 
   float Zoom() const { return zoom_; }
   void SetZoom(float zoom) {
diff --git a/third_party/blink/renderer/core/css/css_math_expression_node.cc b/third_party/blink/renderer/core/css/css_math_expression_node.cc
index f4b031e..35f9d66 100644
--- a/third_party/blink/renderer/core/css/css_math_expression_node.cc
+++ b/third_party/blink/renderer/core/css/css_math_expression_node.cc
@@ -2858,7 +2858,9 @@
   length_resolver.ReferenceAnchor();
   if (AnchorEvaluator* anchor_evaluator =
           length_resolver.GetAnchorEvaluator()) {
-    return anchor_evaluator->Evaluate(query);
+    return anchor_evaluator->Evaluate(query,
+                                      length_resolver.GetPositionAnchor(),
+                                      length_resolver.GetInsetAreaOffsets());
   }
   return std::nullopt;
 }
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 2188bafa..f832a5c 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -2506,7 +2506,7 @@
       field_group: "*",
       field_template: "pointer",
       include_paths: ["third_party/blink/renderer/core/style/shadow_list.h"],
-      wrapper_pointer_name: "scoped_refptr",
+      wrapper_pointer_name: "Member",
       default_value: "nullptr",
       type_name: "ShadowList",
       converter: "ConvertShadowList",
@@ -4327,6 +4327,7 @@
     {
       name: "position-anchor",
       property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal" ],
+      style_builder_custom_functions: ["initial", "inherit", "value"],
       include_paths: ["third_party/blink/renderer/core/style/scoped_css_name.h"],
       type_name: "ScopedCSSName",
       wrapper_pointer_name: "Member",
@@ -4504,12 +4505,13 @@
       property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal"],
       inherited: true,
       name_for_methods: "ScrollbarColor",
-      type_name: "std::optional<StyleScrollbarColor>",
+      type_name: "StyleScrollbarColor",
       converter: "ConvertScrollbarColor",
       keywords: ["auto"],
       field_group: "*",
-      field_template: "external",
-      default_value: "std::optional<StyleScrollbarColor>()",
+      field_template: "pointer",
+      default_value: "nullptr",
+      wrapper_pointer_name: "Member",
       include_paths: ["third_party/blink/renderer/core/style/style_scrollbar_color.h"],
       runtime_flag: "ScrollbarColor",
     },
@@ -5410,7 +5412,7 @@
       field_group: "*",
       field_template: "pointer",
       include_paths: ["third_party/blink/renderer/core/style/shadow_list.h"],
-      wrapper_pointer_name: "scoped_refptr",
+      wrapper_pointer_name: "Member",
       default_value: "nullptr",
       type_name: "ShadowList",
       converter: "ConvertShadowList",
@@ -5809,6 +5811,9 @@
       computed_style_protected_functions: ["getter"],
       default_value: "kNoControlPart",
       type_name: "ControlPart",
+      // appearance needs to be computed before
+      // -internal-appearance-auto-base-select() can be resolved.
+      priority: 1,
     },
     {
       name: "-webkit-appearance",
diff --git a/third_party/blink/renderer/core/css/css_to_length_conversion_data.cc b/third_party/blink/renderer/core/css/css_to_length_conversion_data.cc
index 83e6045..b44b536 100644
--- a/third_party/blink/renderer/core/css/css_to_length_conversion_data.cc
+++ b/third_party/blink/renderer/core/css/css_to_length_conversion_data.cc
@@ -294,9 +294,14 @@
                                   &container_name);
 }
 
-CSSToLengthConversionData::AnchorData::AnchorData(Element* anchored,
-                                                  AnchorEvaluator* evaluator)
-    : evaluator_(evaluator) {
+CSSToLengthConversionData::AnchorData::AnchorData(
+    Element* anchored,
+    AnchorEvaluator* evaluator,
+    const ScopedCSSName* position_anchor,
+    const std::optional<InsetAreaOffsets>& inset_area_offsets)
+    : evaluator_(evaluator),
+      position_anchor_(position_anchor),
+      inset_area_offsets_(inset_area_offsets) {
   if (!evaluator_ && anchored) {
     if (OutOfFlowData* out_of_flow_data = anchored->GetOutOfFlowData()) {
       evaluator_ = &out_of_flow_data->GetAnchorResults();
diff --git a/third_party/blink/renderer/core/css/css_to_length_conversion_data.h b/third_party/blink/renderer/core/css/css_to_length_conversion_data.h
index fbb6f72f..a357fb26 100644
--- a/third_party/blink/renderer/core/css/css_to_length_conversion_data.h
+++ b/third_party/blink/renderer/core/css/css_to_length_conversion_data.h
@@ -38,6 +38,7 @@
 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
 #include "third_party/blink/renderer/core/layout/geometry/axis.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/core/style/inset_area.h"
 #include "third_party/blink/renderer/platform/text/writing_mode.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
@@ -250,11 +251,20 @@
 
    public:
     AnchorData() = default;
-    AnchorData(Element* anchored, AnchorEvaluator*);
+    AnchorData(Element* anchored,
+               AnchorEvaluator*,
+               const ScopedCSSName* position_anchor,
+               const std::optional<InsetAreaOffsets>&);
     AnchorEvaluator* GetEvaluator() const { return evaluator_; }
+    const ScopedCSSName* GetPositionAnchor() const { return position_anchor_; }
+    const std::optional<InsetAreaOffsets>& GetInsetAreaOffsets() const {
+      return inset_area_offsets_;
+    }
 
    private:
     AnchorEvaluator* evaluator_ = nullptr;
+    const ScopedCSSName* position_anchor_ = nullptr;
+    std::optional<InsetAreaOffsets> inset_area_offsets_;
   };
 
   using Flags = uint16_t;
@@ -349,12 +359,21 @@
   void SetLineHeightSize(const LineHeightSize& line_height_size) {
     line_height_size_ = line_height_size;
   }
+  void SetAnchorData(const AnchorData& anchor_data) {
+    anchor_data_ = anchor_data;
+  }
 
   void ReferenceAnchor() const override;
 
   AnchorEvaluator* GetAnchorEvaluator() const override {
     return anchor_data_.GetEvaluator();
   }
+  const ScopedCSSName* GetPositionAnchor() const override {
+    return anchor_data_.GetPositionAnchor();
+  }
+  std::optional<InsetAreaOffsets> GetInsetAreaOffsets() const override {
+    return anchor_data_.GetInsetAreaOffsets();
+  }
 
   // See ContainerSizes::PreCachedCopy.
   //
diff --git a/third_party/blink/renderer/core/css/css_to_length_conversion_data_test.cc b/third_party/blink/renderer/core/css/css_to_length_conversion_data_test.cc
index 6c3b83ab..f7dda468 100644
--- a/third_party/blink/renderer/core/css/css_to_length_conversion_data_test.cc
+++ b/third_party/blink/renderer/core/css/css_to_length_conversion_data_test.cc
@@ -31,7 +31,10 @@
   explicit TestAnchorEvaluator(std::optional<LayoutUnit> result)
       : result_(result) {}
 
-  std::optional<LayoutUnit> Evaluate(const AnchorQuery&) override {
+  std::optional<LayoutUnit> Evaluate(
+      const AnchorQuery&,
+      const ScopedCSSName* position_anchor,
+      const std::optional<InsetAreaOffsets>&) override {
     return result_;
   }
   std::optional<InsetAreaOffsets> ComputeInsetAreaOffsetsForLayout(
@@ -94,7 +97,10 @@
         GetDocument().documentElement()->GetComputedStyle(),
         CSSToLengthConversionData::ViewportSize(GetDocument().GetLayoutView()),
         CSSToLengthConversionData::ContainerSizes(),
-        CSSToLengthConversionData::AnchorData(div, options.anchor_evaluator),
+        CSSToLengthConversionData::AnchorData(
+            div, options.anchor_evaluator,
+            /* position_anchor */ nullptr,
+            /* inset_area_offsets */ std::nullopt),
         options.data_zoom.value_or(div->GetComputedStyle()->EffectiveZoom()),
         options.flags ? *options.flags : ignored_flags_);
   }
@@ -547,7 +553,10 @@
       GetDocument().documentElement()->GetComputedStyle(),
       CSSToLengthConversionData::ViewportSize(GetDocument().GetLayoutView()),
       CSSToLengthConversionData::ContainerSizes(child),
-      CSSToLengthConversionData::AnchorData(child, nullptr),
+      CSSToLengthConversionData::AnchorData(
+          child, nullptr,
+          /* position_anchor */ nullptr,
+          /* inset_area_offsets */ std::nullopt),
       child->GetComputedStyle()->EffectiveZoom(), flags);
 
   ScopedCSSName* name = MakeGarbageCollected<ScopedCSSName>(
diff --git a/third_party/blink/renderer/core/css/css_value.cc b/third_party/blink/renderer/core/css/css_value.cc
index b7e0f6a..06a0126 100644
--- a/third_party/blink/renderer/core/css/css_value.cc
+++ b/third_party/blink/renderer/core/css/css_value.cc
@@ -27,6 +27,7 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 
 #include "third_party/blink/renderer/core/css/css_alternate_value.h"
+#include "third_party/blink/renderer/core/css/css_appearance_auto_base_select_value_pair.h"
 #include "third_party/blink/renderer/core/css/css_axis_value.h"
 #include "third_party/blink/renderer/core/css/css_basic_shape_values.h"
 #include "third_party/blink/renderer/core/css/css_border_image_slice_value.h"
@@ -327,6 +328,9 @@
         return CompareCSSValues<cssvalue::CSSFlipRevertValue>(*this, other);
       case kLightDarkValuePairClass:
         return CompareCSSValues<CSSLightDarkValuePair>(*this, other);
+      case kAppearanceAutoBaseSelectValuePairClass:
+        return CompareCSSValues<CSSAppearanceAutoBaseSelectValuePair>(*this,
+                                                                      other);
       case kScrollClass:
         return CompareCSSValues<cssvalue::CSSScrollValue>(*this, other);
       case kViewClass:
@@ -481,6 +485,8 @@
       return To<cssvalue::CSSFlipRevertValue>(this)->CustomCSSText();
     case kLightDarkValuePairClass:
       return To<CSSLightDarkValuePair>(this)->CustomCSSText();
+    case kAppearanceAutoBaseSelectValuePairClass:
+      return To<CSSAppearanceAutoBaseSelectValuePair>(this)->CustomCSSText();
     case kScrollClass:
       return To<cssvalue::CSSScrollValue>(this)->CustomCSSText();
     case kViewClass:
@@ -728,6 +734,10 @@
     case kLightDarkValuePairClass:
       To<CSSLightDarkValuePair>(this)->TraceAfterDispatch(visitor);
       return;
+    case kAppearanceAutoBaseSelectValuePairClass:
+      To<CSSAppearanceAutoBaseSelectValuePair>(this)->TraceAfterDispatch(
+          visitor);
+      return;
     case kScrollClass:
       To<cssvalue::CSSScrollValue>(this)->TraceAfterDispatch(visitor);
       return;
@@ -774,6 +784,8 @@
       return "ValuePairClass";
     case kLightDarkValuePairClass:
       return "LightDarkValuePairClass";
+    case kAppearanceAutoBaseSelectValuePairClass:
+      return "AppearanceAutoBaseSelectValuePairClass";
     case kScrollClass:
       return "ScrollClass";
     case kViewClass:
diff --git a/third_party/blink/renderer/core/css/css_value.h b/third_party/blink/renderer/core/css/css_value.h
index 883007c7..c5488aa 100644
--- a/third_party/blink/renderer/core/css/css_value.h
+++ b/third_party/blink/renderer/core/css/css_value.h
@@ -203,6 +203,9 @@
   bool IsLightDarkValuePair() const {
     return class_type_ == kLightDarkValuePairClass;
   }
+  bool IsAppearanceAutoBaseSelectValuePair() const {
+    return class_type_ == kAppearanceAutoBaseSelectValuePairClass;
+  }
 
   bool IsScrollValue() const { return class_type_ == kScrollClass; }
   bool IsViewValue() const { return class_type_ == kViewClass; }
@@ -251,6 +254,7 @@
     kURIClass,
     kValuePairClass,
     kLightDarkValuePairClass,
+    kAppearanceAutoBaseSelectValuePairClass,
     kScrollClass,
     kViewClass,
     kRatioClass,
diff --git a/third_party/blink/renderer/core/css/css_value_keywords.json5 b/third_party/blink/renderer/core/css/css_value_keywords.json5
index 21c267f..7e7703b 100644
--- a/third_party/blink/renderer/core/css/css_value_keywords.json5
+++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -894,6 +894,7 @@
     // TODO(crbug.com/1511354): "base-select" is a temporary name for stylable
     // <select>. It should be renamed when a standardized name is chosen.
     "base-select",
+    "-internal-appearance-auto-base-select",
 
     //
     // background-clip/background-origin
diff --git a/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc b/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc
index 866b46b..473e773 100644
--- a/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc
+++ b/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc
@@ -978,6 +978,17 @@
   }
 }
 
+TEST(CSSPropertyParserTest, UAAppearanceAutoBaseSelectSerialization) {
+  auto* ua_context = MakeGarbageCollected<CSSParserContext>(
+      kUASheetMode, SecureContextMode::kInsecureContext);
+  const CSSValue* value = CSSParser::ParseSingleValue(
+      CSSPropertyID::kBackgroundColor,
+      "-internal-appearance-auto-base-select(red, blue)", ua_context);
+  ASSERT_TRUE(value);
+  EXPECT_EQ("-internal-appearance-auto-base-select(red, blue)",
+            value->CssText());
+}
+
 namespace {
 
 bool ParseCSSValue(CSSPropertyID property_id,
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
index 7e431e8..405bab6 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "third_party/blink/renderer/core/css/counter_style_map.h"
+#include "third_party/blink/renderer/core/css/css_appearance_auto_base_select_value_pair.h"
 #include "third_party/blink/renderer/core/css/css_axis_value.h"
 #include "third_party/blink/renderer/core/css/css_basic_shape_values.h"
 #include "third_party/blink/renderer/core/css/css_border_image.h"
@@ -830,6 +831,32 @@
   return MakeGarbageCollected<CSSLightDarkValuePair>(light_value, dark_value);
 }
 
+CSSAppearanceAutoBaseSelectValuePair* ConsumeAppearanceAutoBaseSelectColor(
+    CSSParserTokenRange& range,
+    const CSSParserContext& context,
+    bool accept_quirky_colors,
+    AllowedColorKeywords allowed_color_keywords) {
+  if (range.Peek().FunctionId() !=
+      CSSValueID::kInternalAppearanceAutoBaseSelect) {
+    return nullptr;
+  }
+  CSSParserTokenRange range_copy = range;
+  CSSParserTokenRange arg_range = ConsumeFunction(range_copy);
+  CSSValue* auto_value = ConsumeColor(arg_range, context, accept_quirky_colors,
+                                      allowed_color_keywords);
+  if (!auto_value || !ConsumeCommaIncludingWhitespace(arg_range)) {
+    return nullptr;
+  }
+  CSSValue* base_select_value = ConsumeColor(
+      arg_range, context, accept_quirky_colors, allowed_color_keywords);
+  if (!base_select_value || !arg_range.AtEnd()) {
+    return nullptr;
+  }
+  range = range_copy;
+  return MakeGarbageCollected<CSSAppearanceAutoBaseSelectValuePair>(
+      auto_value, base_select_value);
+}
+
 // https://drafts.csswg.org/css-syntax/#typedef-any-value
 bool IsTokenAllowedForAnyValue(const CSSParserToken& token) {
   switch (token.GetType()) {
@@ -2022,6 +2049,16 @@
     return cssvalue::CSSColor::Create(color);
   }
 
+  if (RuntimeEnabledFeatures::StylableSelectEnabled() &&
+      IsUASheetBehavior(context.Mode())) {
+    if (CSSAppearanceAutoBaseSelectValuePair* auto_base_select_pair =
+            ConsumeAppearanceAutoBaseSelectColor(range, context,
+                                                 /*accept_quirky_colors=*/false,
+                                                 allowed_keywords)) {
+      return auto_base_select_pair;
+    }
+  }
+
   if (IsUASheetBehavior(context.Mode()) ||
       RuntimeEnabledFeatures::CSSLightDarkColorsEnabled()) {
     return ConsumeLightDark(ConsumeColor, range, context,
diff --git a/third_party/blink/renderer/core/css/properties/css_property_test.cc b/third_party/blink/renderer/core/css/properties/css_property_test.cc
index 473d27e5..37d9aaa 100644
--- a/third_party/blink/renderer/core/css/properties/css_property_test.cc
+++ b/third_party/blink/renderer/core/css/properties/css_property_test.cc
@@ -38,7 +38,10 @@
   explicit ModeCheckingAnchorEvaluator(AnchorScope::Mode required_mode)
       : required_mode_(required_mode) {}
 
-  std::optional<LayoutUnit> Evaluate(const AnchorQuery&) override {
+  std::optional<LayoutUnit> Evaluate(
+      const AnchorQuery&,
+      const ScopedCSSName* position_anchor,
+      const std::optional<InsetAreaOffsets>&) override {
     return (required_mode_ == GetMode()) ? std::optional<LayoutUnit>(1)
                                          : std::optional<LayoutUnit>();
   }
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index c88a37ec..612f10d 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -190,7 +190,22 @@
   return MakeGarbageCollected<CSSCustomIdentValue>(*style.PositionAnchor());
 }
 
-// https://github.com/w3c/csswg-drafts/issues/7758
+void PositionAnchor::ApplyInitial(StyleResolverState& state) const {
+  state.SetPositionAnchor(ComputedStyleInitialValues::InitialPositionAnchor());
+}
+
+void PositionAnchor::ApplyInherit(StyleResolverState& state) const {
+  state.SetPositionAnchor(state.ParentStyle()->PositionAnchor());
+}
+
+void PositionAnchor::ApplyValue(StyleResolverState& state,
+                                const CSSValue& value,
+                                ValueMode) const {
+  state.SetPositionAnchor(
+      StyleBuilderConverter::ConvertPositionAnchor(state, value));
+}
+
+// https://drafts.csswg.org/css-anchor-position-1/#position-visibility
 // position-visibility:
 //   always | [ anchors-valid | anchors-visible ] || no-overflow
 // TODO(crbug.com/332933527): Support anchors-valid. For now,
@@ -4534,9 +4549,8 @@
                                           blink::InsetArea inset_area) {
   if (AnchorEvaluator* evaluator =
           state.CssToLengthConversionData().GetAnchorEvaluator()) {
-    state.StyleBuilder().SetInsetAreaOffsets(
-        evaluator->ComputeInsetAreaOffsetsForLayout(
-            state.StyleBuilder().PositionAnchor(), inset_area));
+    state.SetInsetAreaOffsets(evaluator->ComputeInsetAreaOffsetsForLayout(
+        state.StyleBuilder().PositionAnchor(), inset_area));
   }
   state.StyleBuilder().SetHasAnchorFunctions();
 }
@@ -7354,16 +7368,16 @@
     const LayoutObject*,
     bool allow_visited_style,
     CSSValuePhase value_phase) const {
-  std::optional<StyleScrollbarColor> scrollbar_color = style.ScrollbarColor();
-  if (scrollbar_color == std::nullopt) {
+  const StyleScrollbarColor* scrollbar_color = style.ScrollbarColor();
+  if (!scrollbar_color) {
     return CSSIdentifierValue::Create(CSSValueID::kAuto);
   }
 
   CSSValueList* list = CSSValueList::CreateSpaceSeparated();
   list->Append(*ComputedStyleUtils::CurrentColorOrValidColor(
-      style, scrollbar_color.value().GetThumbColor(), value_phase));
+      style, scrollbar_color->GetThumbColor(), value_phase));
   list->Append(*ComputedStyleUtils::CurrentColorOrValidColor(
-      style, scrollbar_color.value().GetTrackColor(), value_phase));
+      style, scrollbar_color->GetTrackColor(), value_phase));
   return list;
 }
 
diff --git a/third_party/blink/renderer/core/css/remote_font_face_source.cc b/third_party/blink/renderer/core/css/remote_font_face_source.cc
index 021723f6..90340fc 100644
--- a/third_party/blink/renderer/core/css/remote_font_face_source.cc
+++ b/third_party/blink/renderer/core/css/remote_font_face_source.cc
@@ -389,7 +389,7 @@
   const SimpleFontData* temporary_font =
       FontCache::Get().GetLastResortFallbackFont(font_description);
   if (!temporary_font) {
-    NOTREACHED();
+    DUMP_WILL_BE_NOTREACHED_NORETURN();
     return nullptr;
   }
   CSSCustomFontData* css_font_data = MakeGarbageCollected<CSSCustomFontData>(
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 bfa5b1a..d5bcf71 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
@@ -2229,21 +2229,22 @@
   return ShadowData(offset, blur, spread, shadow_style, color);
 }
 
-scoped_refptr<ShadowList> StyleBuilderConverter::ConvertShadowList(
-    StyleResolverState& state,
-    const CSSValue& value) {
+ShadowList* StyleBuilderConverter::ConvertShadowList(StyleResolverState& state,
+                                                     const CSSValue& value) {
   if (auto* identifier_value = DynamicTo<CSSIdentifierValue>(value)) {
     DCHECK_EQ(identifier_value->GetValueID(), CSSValueID::kNone);
-    return scoped_refptr<ShadowList>();
+    return nullptr;
   }
 
+  const auto& list = To<CSSValueList>(value);
   ShadowDataVector shadows;
-  for (const auto& item : To<CSSValueList>(value)) {
+  shadows.ReserveInitialCapacity(list.length());
+  for (const auto& item : list) {
     shadows.push_back(
         ConvertShadow(state.CssToLengthConversionData(), &state, *item));
   }
 
-  return ShadowList::Adopt(shadows);
+  return MakeGarbageCollected<ShadowList>(std::move(shadows));
 }
 
 ShapeValue* StyleBuilderConverter::ConvertShapeValue(StyleResolverState& state,
@@ -2382,7 +2383,7 @@
     // StyleColor.
     if (!CanResolveAtComputedValueTime(style_color1) ||
         !CanResolveAtComputedValueTime(style_color2)) {
-      return StyleColor(StyleColor::UnresolvedColorMix(
+      return StyleColor(MakeGarbageCollected<StyleColor::UnresolvedColorMix>(
           color_mix_value, style_color1, style_color2));
     }
 
@@ -3091,12 +3092,12 @@
   return RubyPosition::kBefore;
 }
 
-std::optional<StyleScrollbarColor> StyleBuilderConverter::ConvertScrollbarColor(
+StyleScrollbarColor* StyleBuilderConverter::ConvertScrollbarColor(
     StyleResolverState& state,
     const CSSValue& value) {
   auto* identifier_value = DynamicTo<CSSIdentifierValue>(value);
   if (identifier_value && identifier_value->GetValueID() == CSSValueID::kAuto) {
-    return std::nullopt;
+    return nullptr;
   }
 
   const CSSValueList& list = To<CSSValueList>(value);
@@ -3105,7 +3106,7 @@
   const StyleColor thumb_color = ConvertStyleColor(state, list.First());
   const StyleColor track_color = ConvertStyleColor(state, list.Last());
 
-  return StyleScrollbarColor(thumb_color, track_color);
+  return MakeGarbageCollected<StyleScrollbarColor>(thumb_color, track_color);
 }
 
 ScrollbarGutter StyleBuilderConverter::ConvertScrollbarGutter(
diff --git a/third_party/blink/renderer/core/css/resolver/style_builder_converter.h b/third_party/blink/renderer/core/css/resolver/style_builder_converter.h
index 553d5ca..e9d3b77 100644
--- a/third_party/blink/renderer/core/css/resolver/style_builder_converter.h
+++ b/third_party/blink/renderer/core/css/resolver/style_builder_converter.h
@@ -255,8 +255,7 @@
   static ShadowData ConvertShadow(const CSSToLengthConversionData&,
                                   StyleResolverState*,
                                   const CSSValue&);
-  static scoped_refptr<ShadowList> ConvertShadowList(StyleResolverState&,
-                                                     const CSSValue&);
+  static ShadowList* ConvertShadowList(StyleResolverState&, const CSSValue&);
   static ShapeValue* ConvertShapeValue(StyleResolverState&, const CSSValue&);
   static float ConvertSpacing(StyleResolverState&, const CSSValue&);
   template <CSSValueID IdForNone>
@@ -345,9 +344,8 @@
   static RubyPosition ConvertRubyPosition(StyleResolverState& state,
                                           const CSSValue& value);
 
-  static std::optional<StyleScrollbarColor> ConvertScrollbarColor(
-      StyleResolverState& state,
-      const CSSValue& value);
+  static StyleScrollbarColor* ConvertScrollbarColor(StyleResolverState& state,
+                                                    const CSSValue& value);
 
   static ScrollbarGutter ConvertScrollbarGutter(StyleResolverState& state,
                                                 const CSSValue& value);
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.cc b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
index 4a330fa..121fc22 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/core/animation/invalidatable_interpolation.h"
 #include "third_party/blink/renderer/core/animation/property_handle.h"
 #include "third_party/blink/renderer/core/animation/transition_interpolation.h"
+#include "third_party/blink/renderer/core/css/css_appearance_auto_base_select_value_pair.h"
 #include "third_party/blink/renderer/core/css/css_cyclic_variable_value.h"
 #include "third_party/blink/renderer/core/css/css_flip_revert_value.h"
 #include "third_party/blink/renderer/core/css/css_font_selector.h"
@@ -987,6 +988,19 @@
   if (const auto* v = DynamicTo<CSSFlipRevertValue>(result)) {
     return ResolveFlipRevert(*v, priority, origin, resolver);
   }
+  if (auto* auto_base_select_pair =
+          DynamicTo<CSSAppearanceAutoBaseSelectValuePair>(value)) {
+    // The UA stylesheet only uses -internal-auto-base-select() on select
+    // elements, which is currently the only element which supports
+    // appearance:base-select.
+    CHECK(IsA<HTMLSelectElement>(state_.GetElement()));
+
+    if (state_.StyleBuilder().HasBaseSelectAppearance()) {
+      return &auto_base_select_pair->Second();
+    } else {
+      return &auto_base_select_pair->First();
+    }
+  }
 
   resolver.CollectFlags(property, origin);
 
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index 5ef8098..5c443f8 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -3029,7 +3029,7 @@
     PROPAGATE_FROM(document_element_style, ScrollbarWidth, SetScrollbarWidth,
                    EScrollbarWidth::kAuto);
     PROPAGATE_FROM(document_element_style, ScrollbarColor, SetScrollbarColor,
-                   std::nullopt);
+                   nullptr);
     PROPAGATE_FROM(document_element_style, ForcedColorAdjust,
                    SetForcedColorAdjust, EForcedColorAdjust::kAuto);
     if (RuntimeEnabledFeatures::UsedColorSchemeRootScrollbarsEnabled()) {
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver_state.cc b/third_party/blink/renderer/core/css/resolver/style_resolver_state.cc
index ad2dc0d8..6194e869 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver_state.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver_state.cc
@@ -161,7 +161,9 @@
       *style_builder_, ParentStyle(), RootElementStyle(),
       GetDocument().GetStyleEngine().GetViewportSize(),
       CSSToLengthConversionData::ContainerSizes(container_unit_context_),
-      CSSToLengthConversionData::AnchorData(styled_element_, anchor_evaluator_),
+      CSSToLengthConversionData::AnchorData(styled_element_, anchor_evaluator_,
+                                            StyleBuilder().PositionAnchor(),
+                                            StyleBuilder().InsetAreaOffsets()),
       StyleBuilder().EffectiveZoom(), length_conversion_flags_);
   element_style_resources_.UpdateLengthConversionData(
       &css_to_length_conversion_data_);
@@ -180,8 +182,9 @@
       GetDocument().GetLayoutView());
   CSSToLengthConversionData::ContainerSizes container_sizes(
       container_unit_context_);
-  CSSToLengthConversionData::AnchorData anchor_data(styled_element_,
-                                                    anchor_evaluator_);
+  CSSToLengthConversionData::AnchorData anchor_data(
+      styled_element_, anchor_evaluator_, StyleBuilder().PositionAnchor(),
+      StyleBuilder().InsetAreaOffsets());
   return CSSToLengthConversionData(
       StyleBuilder().GetWritingMode(), font_sizes, line_height_size,
       viewport_size, container_sizes, anchor_data, 1, length_conversion_flags_);
@@ -277,6 +280,27 @@
   }
 }
 
+void StyleResolverState::SetPositionAnchor(ScopedCSSName* position_anchor) {
+  if (StyleBuilder().PositionAnchor() != position_anchor) {
+    StyleBuilder().SetPositionAnchor(position_anchor);
+    css_to_length_conversion_data_.SetAnchorData(
+        CSSToLengthConversionData::AnchorData(
+            styled_element_, anchor_evaluator_, position_anchor,
+            StyleBuilder().InsetAreaOffsets()));
+  }
+}
+
+void StyleResolverState::SetInsetAreaOffsets(
+    const std::optional<InsetAreaOffsets>& inset_area_offsets) {
+  if (StyleBuilder().InsetAreaOffsets() != inset_area_offsets) {
+    StyleBuilder().SetInsetAreaOffsets(inset_area_offsets);
+    css_to_length_conversion_data_.SetAnchorData(
+        CSSToLengthConversionData::AnchorData(
+            styled_element_, anchor_evaluator_, StyleBuilder().PositionAnchor(),
+            inset_area_offsets));
+  }
+}
+
 CSSParserMode StyleResolverState::GetParserMode() const {
   return GetDocument().InQuirksMode() ? kHTMLQuirksMode : kHTMLStandardMode;
 }
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver_state.h b/third_party/blink/renderer/core/css/resolver/style_resolver_state.h
index 154c4fea..c537199 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver_state.h
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver_state.h
@@ -183,6 +183,8 @@
   void SetEffectiveZoom(float);
   void SetWritingMode(WritingMode);
   void SetTextOrientation(ETextOrientation);
+  void SetPositionAnchor(ScopedCSSName*);
+  void SetInsetAreaOffsets(const std::optional<InsetAreaOffsets>&);
 
   CSSParserMode GetParserMode() const;
 
diff --git a/third_party/blink/renderer/core/css/result_caching_anchor_evaluator.cc b/third_party/blink/renderer/core/css/result_caching_anchor_evaluator.cc
index e858bd8..80c38ac0 100644
--- a/third_party/blink/renderer/core/css/result_caching_anchor_evaluator.cc
+++ b/third_party/blink/renderer/core/css/result_caching_anchor_evaluator.cc
@@ -16,15 +16,18 @@
 }
 
 std::optional<LayoutUnit> ResultCachingAnchorEvaluator::Evaluate(
-    const AnchorQuery& query) {
+    const AnchorQuery& query,
+    const ScopedCSSName* position_anchor,
+    const std::optional<InsetAreaOffsets>& inset_area_offsets) {
   if (GetMode() == AnchorScope::Mode::kNone) {
     return std::nullopt;
   }
   // Forward mode to inner evaluator.
-  AnchorScope anchor_scope(GetMode(), GetInsetAreaOffsets(),
-                           GetPositionAnchorName(), evaluator_);
+  AnchorScope anchor_scope(GetMode(), evaluator_);
   std::optional<LayoutUnit> result =
-      evaluator_ ? evaluator_->Evaluate(query) : std::optional<LayoutUnit>();
+      evaluator_
+          ? evaluator_->Evaluate(query, position_anchor, inset_area_offsets)
+          : std::optional<LayoutUnit>();
   results_.Set(GetMode(), query, result);
   return result;
 }
diff --git a/third_party/blink/renderer/core/css/result_caching_anchor_evaluator.h b/third_party/blink/renderer/core/css/result_caching_anchor_evaluator.h
index bb8f94b6..24e70b0 100644
--- a/third_party/blink/renderer/core/css/result_caching_anchor_evaluator.h
+++ b/third_party/blink/renderer/core/css/result_caching_anchor_evaluator.h
@@ -28,7 +28,10 @@
  public:
   ResultCachingAnchorEvaluator(AnchorEvaluator*, AnchorResults&);
 
-  std::optional<LayoutUnit> Evaluate(const AnchorQuery&) override;
+  std::optional<LayoutUnit> Evaluate(
+      const AnchorQuery&,
+      const ScopedCSSName* position_anchor,
+      const std::optional<InsetAreaOffsets>&) override;
   std::optional<InsetAreaOffsets> ComputeInsetAreaOffsetsForLayout(
       const ScopedCSSName* position_anchor,
       InsetArea inset_area) override;
diff --git a/third_party/blink/renderer/core/css/style_color.cc b/third_party/blink/renderer/core/css/style_color.cc
index c0a8271..084528d 100644
--- a/third_party/blink/renderer/core/css/style_color.cc
+++ b/third_party/blink/renderer/core/css/style_color.cc
@@ -16,8 +16,8 @@
     const StyleColor& c2)
     : color_interpolation_space_(in->ColorInterpolationSpace()),
       hue_interpolation_method_(in->HueInterpolationMethod()),
-      color1_(c1),
-      color2_(c2) {
+      color1_(c1.color_or_unresolved_color_mix_),
+      color2_(c2.color_or_unresolved_color_mix_) {
   if (c1.IsUnresolvedColorMixFunction()) {
     color1_type_ = UnderlyingColorType::kColorMix;
   } else if (c1.IsCurrentColor()) {
@@ -40,72 +40,6 @@
       in->Percentage1(), in->Percentage2(), percentage_, alpha_multiplier_);
 }
 
-StyleColor::UnresolvedColorMix::UnresolvedColorMix(
-    const UnresolvedColorMix& other)
-    : color_interpolation_space_(other.color_interpolation_space_),
-      hue_interpolation_method_(other.hue_interpolation_method_),
-      percentage_(other.percentage_),
-      alpha_multiplier_(other.alpha_multiplier_),
-      color1_type_(other.color1_type_),
-      color2_type_(other.color2_type_) {
-  if (color1_type_ == UnderlyingColorType::kColorMix) {
-    new (&color1_.unresolved_color_mix) std::unique_ptr<UnresolvedColorMix>(
-        new UnresolvedColorMix(*other.color1_.unresolved_color_mix));
-  } else if (color1_type_ == UnderlyingColorType::kColor) {
-    color1_.color = other.color1_.color;
-  }
-
-  if (color2_type_ == UnderlyingColorType::kColorMix) {
-    new (&color2_.unresolved_color_mix) std::unique_ptr<UnresolvedColorMix>(
-        new UnresolvedColorMix(*other.color2_.unresolved_color_mix));
-  } else if (color2_type_ == UnderlyingColorType::kColor) {
-    color2_.color = other.color2_.color;
-  }
-}
-
-StyleColor::UnresolvedColorMix& StyleColor::UnresolvedColorMix::operator=(
-    const StyleColor::UnresolvedColorMix& other) {
-  if (this == &other) {
-    return *this;
-  }
-  color_interpolation_space_ = other.color_interpolation_space_;
-  hue_interpolation_method_ = other.hue_interpolation_method_;
-  percentage_ = other.percentage_;
-  alpha_multiplier_ = other.alpha_multiplier_;
-
-  if (other.color1_type_ == UnderlyingColorType::kColorMix) {
-    if (color1_type_ == UnderlyingColorType::kColorMix) {
-      // Avoid leaking an UnresolvedColorMix that is already stored on "this"
-      color1_.unresolved_color_mix.reset();
-      color1_.unresolved_color_mix = std::make_unique<UnresolvedColorMix>(
-          *other.color1_.unresolved_color_mix);
-    } else {
-      new (&color1_.unresolved_color_mix) std::unique_ptr<UnresolvedColorMix>(
-          new UnresolvedColorMix(*other.color1_.unresolved_color_mix));
-    }
-  } else if (other.color1_type_ == UnderlyingColorType::kColor) {
-    color1_.color = other.color1_.color;
-  }
-
-  if (other.color2_type_ == UnderlyingColorType::kColorMix) {
-    if (color2_type_ == UnderlyingColorType::kColorMix) {
-      // Avoid leaking an UnresolvedColorMix that is already stored on "this"
-      color2_.unresolved_color_mix.reset();
-      color2_.unresolved_color_mix = std::make_unique<UnresolvedColorMix>(
-          *other.color2_.unresolved_color_mix);
-    } else {
-      new (&color2_.unresolved_color_mix) std::unique_ptr<UnresolvedColorMix>(
-          new UnresolvedColorMix(*other.color2_.unresolved_color_mix));
-    }
-  } else if (other.color2_type_ == UnderlyingColorType::kColor) {
-    color2_.color = other.color2_.color;
-  }
-
-  color1_type_ = other.color1_type_;
-  color2_type_ = other.color2_type_;
-  return *this;
-}
-
 Color StyleColor::UnresolvedColorMix::Resolve(
     const Color& current_color) const {
   Color c1 = current_color;
@@ -131,99 +65,8 @@
                              alpha_multiplier_);
 }
 
-StyleColor::ColorOrUnresolvedColorMix::ColorOrUnresolvedColorMix(
-    UnresolvedColorMix color_mix) {
-  new (&unresolved_color_mix)
-      std::unique_ptr<UnresolvedColorMix>(new UnresolvedColorMix(color_mix));
-}
-
-StyleColor::ColorOrUnresolvedColorMix::ColorOrUnresolvedColorMix(
-    const StyleColor style_color) {
-  if (style_color.IsUnresolvedColorMixFunction()) {
-    new (&unresolved_color_mix) std::unique_ptr<UnresolvedColorMix>(
-        new UnresolvedColorMix(style_color.GetUnresolvedColorMix()));
-  } else {
-    color = style_color.color_or_unresolved_color_mix_.color;
-  }
-}
-
-StyleColor::StyleColor(const StyleColor& other)
-    : color_keyword_(other.color_keyword_) {
-  if (IsUnresolvedColorMixFunction()) {
-    new (&color_or_unresolved_color_mix_.unresolved_color_mix)
-        std::unique_ptr<UnresolvedColorMix>(new UnresolvedColorMix(
-            *other.color_or_unresolved_color_mix_.unresolved_color_mix));
-  } else {
-    color_or_unresolved_color_mix_.color =
-        other.color_or_unresolved_color_mix_.color;
-  }
-}
-
-StyleColor& StyleColor::operator=(const StyleColor& other) {
-  if (this == &other) {
-    return *this;
-  }
-  if (other.IsUnresolvedColorMixFunction()) {
-    if (IsUnresolvedColorMixFunction()) {
-      color_or_unresolved_color_mix_.unresolved_color_mix.reset();
-      color_or_unresolved_color_mix_.unresolved_color_mix =
-          std::make_unique<UnresolvedColorMix>(
-              *other.color_or_unresolved_color_mix_.unresolved_color_mix);
-    } else {
-      new (&color_or_unresolved_color_mix_.unresolved_color_mix)
-          std::unique_ptr<UnresolvedColorMix>(new UnresolvedColorMix(
-              *other.color_or_unresolved_color_mix_.unresolved_color_mix));
-    }
-  } else {
-    if (IsUnresolvedColorMixFunction()) {
-      color_or_unresolved_color_mix_.unresolved_color_mix.reset();
-    }
-    color_or_unresolved_color_mix_.color =
-        other.color_or_unresolved_color_mix_.color;
-  }
-  color_keyword_ = other.color_keyword_;
-  return *this;
-}
-
-StyleColor::StyleColor(StyleColor&& other)
-    : color_keyword_(other.color_keyword_) {
-  if (other.IsUnresolvedColorMixFunction()) {
-    new (&color_or_unresolved_color_mix_.unresolved_color_mix)
-        std::unique_ptr<UnresolvedColorMix>(std::move(
-            other.color_or_unresolved_color_mix_.unresolved_color_mix));
-  } else {
-    color_or_unresolved_color_mix_.color =
-        other.color_or_unresolved_color_mix_.color;
-  }
-}
-
-StyleColor& StyleColor::operator=(StyleColor&& other) {
-  if (this == &other) {
-    return *this;
-  }
-  if (other.IsUnresolvedColorMixFunction()) {
-    if (IsUnresolvedColorMixFunction()) {
-      color_or_unresolved_color_mix_.unresolved_color_mix.reset();
-      color_or_unresolved_color_mix_.unresolved_color_mix =
-          std::make_unique<UnresolvedColorMix>(std::move(
-              *other.color_or_unresolved_color_mix_.unresolved_color_mix));
-    } else {
-      new (&color_or_unresolved_color_mix_.unresolved_color_mix)
-          std::unique_ptr<UnresolvedColorMix>((std::move(
-              other.color_or_unresolved_color_mix_.unresolved_color_mix)));
-    }
-  } else {
-    color_or_unresolved_color_mix_.color =
-        other.color_or_unresolved_color_mix_.color;
-  }
-  color_keyword_ = other.color_keyword_;
-  return *this;
-}
-
-StyleColor::~StyleColor() {
-  if (IsUnresolvedColorMixFunction()) {
-    color_or_unresolved_color_mix_.unresolved_color_mix.reset();
-  }
+void StyleColor::ColorOrUnresolvedColorMix::Trace(Visitor* visitor) const {
+  visitor->Trace(unresolved_color_mix);
 }
 
 Color StyleColor::Resolve(const Color& current_color,
diff --git a/third_party/blink/renderer/core/css/style_color.h b/third_party/blink/renderer/core/css/style_color.h
index 1d53359..36d9bfaf 100644
--- a/third_party/blink/renderer/core/css/style_color.h
+++ b/third_party/blink/renderer/core/css/style_color.h
@@ -54,20 +54,22 @@
   // value time (such as "currentcolor"), we need to store them here and
   // resolve them to individual colors later.
   class UnresolvedColorMix;
-  union ColorOrUnresolvedColorMix {
+  struct ColorOrUnresolvedColorMix {
+    DISALLOW_NEW();
+
+   public:
     ColorOrUnresolvedColorMix() : color(Color::kTransparent) {}
     explicit ColorOrUnresolvedColorMix(Color color) : color(color) {}
-    explicit ColorOrUnresolvedColorMix(const StyleColor style_color);
-    explicit ColorOrUnresolvedColorMix(UnresolvedColorMix color_mix);
-    // Since an instance ColorOrUnresolvedColorMix does not know whether it
-    // contains a color or an UnresolvedColorMix, release of
-    // unresolved_color_mix is left to StyleColor::~StyleColor().
-    ~ColorOrUnresolvedColorMix() {}
+    explicit ColorOrUnresolvedColorMix(const UnresolvedColorMix* color_mix)
+        : unresolved_color_mix(color_mix) {}
+
+    CORE_EXPORT void Trace(Visitor*) const;
 
     Color color;
-    std::unique_ptr<UnresolvedColorMix> unresolved_color_mix;
+    Member<const UnresolvedColorMix> unresolved_color_mix;
   };
-  class UnresolvedColorMix {
+
+  class UnresolvedColorMix : public GarbageCollected<UnresolvedColorMix> {
    public:
     enum class UnderlyingColorType {
       kColor,
@@ -79,8 +81,12 @@
                        const StyleColor& c1,
                        const StyleColor& c2);
     UnresolvedColorMix();
-    UnresolvedColorMix(const UnresolvedColorMix& other);
-    UnresolvedColorMix& operator=(const UnresolvedColorMix& other);
+
+    void Trace(Visitor* visitor) const {
+      visitor->Trace(color1_);
+      visitor->Trace(color2_);
+    }
+
     Color Resolve(const Color& current_color) const;
 
     static bool Equals(const ColorOrUnresolvedColorMix& first,
@@ -132,7 +138,7 @@
       : color_keyword_(CSSValueID::kInvalid),
         color_or_unresolved_color_mix_(color) {}
   explicit StyleColor(CSSValueID keyword) : color_keyword_(keyword) {}
-  explicit StyleColor(UnresolvedColorMix color_mix)
+  explicit StyleColor(const UnresolvedColorMix* color_mix)
       : color_keyword_(CSSValueID::kColorMix),
         color_or_unresolved_color_mix_(color_mix) {}
   // We need to store the color and keyword for system colors to be able to
@@ -141,13 +147,9 @@
   StyleColor(Color color, CSSValueID keyword)
       : color_keyword_(keyword), color_or_unresolved_color_mix_(color) {}
 
-  // All copy/move/assignment operators are necessary to handle the potential
-  // unique pointer in color_or_unresolved_color_mix_.
-  StyleColor(const StyleColor& other);
-  StyleColor& operator=(const StyleColor& other);
-  StyleColor& operator=(StyleColor&& other);
-  StyleColor(StyleColor&&);
-  ~StyleColor();
+  void Trace(Visitor* visitor) const {
+    visitor->Trace(color_or_unresolved_color_mix_);
+  }
 
   static StyleColor CurrentColor() { return StyleColor(); }
 
@@ -161,7 +163,7 @@
     return IsSystemColorIncludingDeprecated(color_keyword_);
   }
   bool IsSystemColor() const { return IsSystemColor(color_keyword_); }
-  UnresolvedColorMix GetUnresolvedColorMix() const {
+  const UnresolvedColorMix& GetUnresolvedColorMix() const {
     DCHECK(IsUnresolvedColorMixFunction());
     return *color_or_unresolved_color_mix_.unresolved_color_mix;
   }
diff --git a/third_party/blink/renderer/core/css/style_rule.cc b/third_party/blink/renderer/core/css/style_rule.cc
index 158c279..161c51d9 100644
--- a/third_party/blink/renderer/core/css/style_rule.cc
+++ b/third_party/blink/renderer/core/css/style_rule.cc
@@ -174,7 +174,7 @@
       To<StyleRulePositionTry>(this)->TraceAfterDispatch(visitor);
       return;
   }
-  NOTREACHED();
+  DUMP_WILL_BE_NOTREACHED_NORETURN();
 }
 
 void StyleRuleBase::FinalizeGarbageCollectedObject() {
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 80c6270..22b6bd9 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -1235,6 +1235,9 @@
 Element* Element::anchorElement() const {
   // TODO(crbug.com/1425215): Fix GetElementAttribute() for out-of-tree-scope
   // elements, so that we can remove the hack below.
+  if (!RuntimeEnabledFeatures::HTMLAnchorAttributeEnabled()) {
+    return nullptr;
+  }
   if (!IsInTreeScope()) {
     return nullptr;
   }
@@ -1242,6 +1245,7 @@
 }
 
 void Element::setAnchorElement(Element* new_element) {
+  CHECK(RuntimeEnabledFeatures::HTMLAnchorAttributeEnabled());
   SetElementAttribute(html_names::kAnchorAttr, new_element);
   EnsureAnchorElementObserver().Notify();
 }
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 05c2af65..b255ea0 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -1363,6 +1363,8 @@
 
   // Retrieves the element pointed to by this element's 'anchor' content
   // attribute, if that element exists.
+  // TODO(crbug.com/40059176) If the HTMLAnchorAttribute feature is disabled,
+  // this will return nullptr;
   Element* anchorElement() const;
   void setAnchorElement(Element*);
 
diff --git a/third_party/blink/renderer/core/dom/element.idl b/third_party/blink/renderer/core/dom/element.idl
index f7a0052..53ad9ffc 100644
--- a/third_party/blink/renderer/core/dom/element.idl
+++ b/third_party/blink/renderer/core/dom/element.idl
@@ -143,7 +143,7 @@
     readonly attribute long clientHeight;
 
     // Used by both Anchor Positioning and Popover
-    [CEReactions,RuntimeEnabled=CSSAnchorPositioning] attribute Element? anchorElement;
+    [CEReactions,RuntimeEnabled=HTMLAnchorAttribute] attribute Element? anchorElement;
 
     // Non-standard API
     [MeasureAs=ElementScrollIntoViewIfNeeded] void scrollIntoViewIfNeeded(optional boolean centerIfNeeded);
diff --git a/third_party/blink/renderer/core/editing/markers/document_marker_controller.cc b/third_party/blink/renderer/core/editing/markers/document_marker_controller.cc
index 00b6ae8..5cf67047 100644
--- a/third_party/blink/renderer/core/editing/markers/document_marker_controller.cc
+++ b/third_party/blink/renderer/core/editing/markers/document_marker_controller.cc
@@ -540,7 +540,7 @@
 
   if (start > end) {
     // TODO(crbug/1114021): Investigate why this might happen.
-    NOTREACHED() << "|start| should be before |end|.";
+    DUMP_WILL_BE_NOTREACHED_NORETURN() << "|start| should be before |end|.";
     return nullptr;
   }
 
diff --git a/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller.cc b/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller.cc
index 38299cc..e6d732d 100644
--- a/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller.cc
+++ b/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller.cc
@@ -280,7 +280,7 @@
     static auto* state_data = base::debug::AllocateCrashKeyString(
         "spellchecker-state-on-invocation", base::debug::CrashKeySize::Size32);
     base::debug::SetCrashKeyString(state_data, GetStateAsString());
-    NOTREACHED() << GetStateAsString();
+    DUMP_WILL_BE_NOTREACHED_NORETURN() << GetStateAsString();
     Deactivate();
   }
 }
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 2a18861b..9b7f985d 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -2081,7 +2081,7 @@
   // Prevent reentrance.
   // TODO(vmpstr): Should we just have a DCHECK instead here?
   if (UNLIKELY(IsUpdatingLifecycle())) {
-    NOTREACHED()
+    DUMP_WILL_BE_NOTREACHED_NORETURN()
         << "LocalFrameView::updateLifecyclePhasesInternal() reentrance";
     return false;
   }
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index a4619a0..dc26385 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -345,18 +345,6 @@
   }
 
   void SpoolSinglePage(cc::PaintCanvas* canvas, wtf_size_t page_number) {
-    DispatchEventsForPrintingOnAllFrames();
-    if (!GetFrame()->GetDocument() ||
-        !GetFrame()->GetDocument()->GetLayoutView()) {
-      return;
-    }
-
-    GetFrame()->View()->UpdateLifecyclePhasesForPrinting();
-    if (!GetFrame()->GetDocument() ||
-        !GetFrame()->GetDocument()->GetLayoutView()) {
-      return;
-    }
-
     // The page rect gets scaled and translated, so specify the entire
     // print content area here as the recording rect.
     PaintRecordBuilder builder;
@@ -371,16 +359,6 @@
   void SpoolPagesWithBoundariesForTesting(cc::PaintCanvas* canvas,
                                           const gfx::Size& spool_size_in_pixels,
                                           const WebVector<uint32_t>* pages) {
-    DispatchEventsForPrintingOnAllFrames();
-    if (!GetFrame()->GetDocument() ||
-        !GetFrame()->GetDocument()->GetLayoutView())
-      return;
-
-    GetFrame()->View()->UpdateLifecyclePhasesForPrinting();
-    if (!GetFrame()->GetDocument() ||
-        !GetFrame()->GetDocument()->GetLayoutView())
-      return;
-
     gfx::Rect all_pages_rect(spool_size_in_pixels);
 
     PaintRecordBuilder builder;
@@ -448,6 +426,15 @@
 
  protected:
   virtual void SpoolPage(GraphicsContext& context, wtf_size_t page_number) {
+    DispatchEventsForPrintingOnAllFrames();
+    if (!IsFrameValid()) {
+      return;
+    }
+
+    auto* frame_view = GetFrame()->View();
+    DCHECK(frame_view);
+    frame_view->UpdateLifecyclePhasesForPrinting();
+
     if (!IsFrameValid() || page_number >= PageCount()) {
       // TODO(crbug.com/452672): The number of pages may change after layout for
       // pagination.
@@ -456,8 +443,6 @@
     gfx::Rect page_rect = PageRect(page_number);
     AffineTransform transform;
 
-    auto* frame_view = GetFrame()->View();
-    DCHECK(frame_view);
     const LayoutView* layout_view = frame_view->GetLayoutView();
 
     // Layout may have used a larger viewport size in order to fit more
diff --git a/third_party/blink/renderer/core/highlight/highlight_style_utils.h b/third_party/blink/renderer/core/highlight/highlight_style_utils.h
index 3d96431..da3334a 100644
--- a/third_party/blink/renderer/core/highlight/highlight_style_utils.h
+++ b/third_party/blink/renderer/core/highlight/highlight_style_utils.h
@@ -44,9 +44,14 @@
                     HighlightColorProperty::kCurrentColor,
                     HighlightColorProperty::kTextDecorationColor>;
   struct HighlightTextPaintStyle {
+    DISALLOW_NEW();
+
+   public:
     TextPaintStyle style;
     Color text_decoration_color;
     HighlightColorPropertySet properties_using_current_color;
+
+    void Trace(Visitor* visitor) const { visitor->Trace(style); }
   };
 
   static Color ResolveColor(const Document&,
diff --git a/third_party/blink/renderer/core/highlight/highlight_style_utils_test.cc b/third_party/blink/renderer/core/highlight/highlight_style_utils_test.cc
index ca803abe..0163fafdd1 100644
--- a/third_party/blink/renderer/core/highlight/highlight_style_utils_test.cc
+++ b/third_party/blink/renderer/core/highlight/highlight_style_utils_test.cc
@@ -212,10 +212,6 @@
       HighlightStyleUtils::HighlightPaintingStyle(
           GetDocument(), div_style, div_text, kPseudoIdHighlight, paint_style,
           paint_info, AtomicString("highlight1"));
-  HighlightStyleUtils::HighlightTextPaintStyle selection_paint_style =
-      HighlightStyleUtils::HighlightPaintingStyle(GetDocument(), div_style,
-                                                  div_text, kPseudoIdSelection,
-                                                  paint_style, paint_info);
 
   EXPECT_TRUE(highlight_paint_style.properties_using_current_color.Has(
       HighlightStyleUtils::HighlightColorProperty::kCurrentColor));
@@ -247,6 +243,10 @@
   EXPECT_TRUE(highlight_paint_style.properties_using_current_color.Has(
       HighlightStyleUtils::HighlightColorProperty::kSelectionDecorationColor));
 #else
+  HighlightStyleUtils::HighlightTextPaintStyle selection_paint_style =
+      HighlightStyleUtils::HighlightPaintingStyle(GetDocument(), div_style,
+                                                  div_text, kPseudoIdSelection,
+                                                  paint_style, paint_info);
   // Selection uses explicit default colors.
   EXPECT_TRUE(selection_paint_style.properties_using_current_color.empty());
 #endif
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 67aacdf..fc82697 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
@@ -601,6 +601,19 @@
       continue;
     }
 
+    // Descendants <option>s of a child <datalist> should be included in the
+    // native popup.
+    if (RuntimeEnabledFeatures::StylableSelectEnabled() &&
+        IsA<HTMLDataListElement>(*current_html_element)) {
+      for (Node& datalist_descendant :
+           NodeTraversal::DescendantsOf(*current_html_element)) {
+        if (IsA<HTMLOptionElement>(datalist_descendant) ||
+            IsA<HTMLHRElement>(datalist_descendant)) {
+          list_items_.push_back(To<HTMLElement>(datalist_descendant));
+        }
+      }
+    }
+
     // We should ignore nested optgroup elements. The HTML parser flatten
     // them.  However we need to ignore nested optgroups built by DOM APIs.
     // This behavior matches to IE and Firefox.
diff --git a/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc b/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
index cd49b51e..a8ebf4a 100644
--- a/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
+++ b/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
@@ -164,7 +164,7 @@
   style_builder.SetShouldIgnoreOverflowPropertyForInlineBlockBaseline();
 
   if (!IsA<HTMLTextAreaElement>(host)) {
-    style_builder.SetScrollbarColor(std::nullopt);
+    style_builder.SetScrollbarColor(nullptr);
     style_builder.SetWhiteSpace(EWhiteSpace::kPre);
     style_builder.SetOverflowWrap(EOverflowWrap::kNormal);
     style_builder.SetTextOverflow(ToTextControl(host)->ValueForTextOverflow());
diff --git a/third_party/blink/renderer/core/html/resources/stylable_select.css b/third_party/blink/renderer/core/html/resources/stylable_select.css
index 1e05707..69c4914d 100644
--- a/third_party/blink/renderer/core/html/resources/stylable_select.css
+++ b/third_party/blink/renderer/core/html/resources/stylable_select.css
@@ -6,6 +6,10 @@
  * feature is enabled.
  */
 
+select {
+  background-color: -internal-appearance-auto-base-select(Field, transparent);
+}
+
 /* Undo unwanted styles from select rules */
 select > button,
 select > datalist,
diff --git a/third_party/blink/renderer/core/html/resources/stylable_select_forced_colors.css b/third_party/blink/renderer/core/html/resources/stylable_select_forced_colors.css
new file mode 100644
index 0000000..0ae9889
--- /dev/null
+++ b/third_party/blink/renderer/core/html/resources/stylable_select_forced_colors.css
@@ -0,0 +1,12 @@
+/* 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.
+ *
+ * The default style sheet used to render <select> when the StylableSelect
+ * feature is enabled and the forced colors are enabled. It can be merged into
+ * forced_colors.css when StylableSelect is enabled and the flag is removed.
+ */
+
+select {
+  background-color: -internal-appearance-auto-base-select(Canvas, transparent);
+}
diff --git a/third_party/blink/renderer/core/html/resources/stylable_select_linux.css b/third_party/blink/renderer/core/html/resources/stylable_select_linux.css
new file mode 100644
index 0000000..3d518a4
--- /dev/null
+++ b/third_party/blink/renderer/core/html/resources/stylable_select_linux.css
@@ -0,0 +1,13 @@
+/* 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.
+ *
+ * The default style sheet used to render <select> when the StylableSelect
+ * feature is enabled and the platform is Linux. It can be merged into
+ * linux.css when StylableSelect is enabled and the flag is removed.
+ */
+
+select:not(:-internal-list-box) {
+    /* Selects with popup menus look like buttons with a drop down triangle on Linux. */
+    background-color: -internal-appearance-auto-base-select(ButtonFace, transparent);
+}
diff --git a/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc b/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc
index 0b0bbb7..95ca0847 100644
--- a/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc
+++ b/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc
@@ -350,34 +350,25 @@
 }
 
 std::optional<LayoutUnit> AnchorEvaluatorImpl::Evaluate(
-    const class AnchorQuery& anchor_query) {
+    const class AnchorQuery& anchor_query,
+    const ScopedCSSName* position_anchor,
+    const std::optional<InsetAreaOffsets>& inset_area_offsets) {
   switch (anchor_query.Type()) {
     case CSSAnchorQueryType::kAnchor:
       return EvaluateAnchor(anchor_query.AnchorSpecifier(),
                             anchor_query.AnchorSide(),
-                            anchor_query.AnchorSidePercentageOrZero());
+                            anchor_query.AnchorSidePercentageOrZero(),
+                            position_anchor, inset_area_offsets);
     case CSSAnchorQueryType::kAnchorSize:
       return EvaluateAnchorSize(anchor_query.AnchorSpecifier(),
-                                anchor_query.AnchorSize());
-  }
-}
-
-void AnchorEvaluatorImpl::ValidateDefaultAnchor() const {
-  if (!base::ValuesEquivalent(GetPositionAnchorName(),
-                              position_anchor_specifier_)) {
-    position_anchor_specifier_ = GetPositionAnchorName();
-    default_anchor_.reset();
-    default_anchor_scroll_container_layer_.reset();
-    // The effect of an applied inset-area depends on the position-anchor.
-    ApplyInsetAreaOffsets();
+                                anchor_query.AnchorSize(), position_anchor);
   }
 }
 
 const LogicalAnchorReference* AnchorEvaluatorImpl::ResolveAnchorReference(
-    const AnchorSpecifierValue& anchor_specifier) const {
-  ValidateDefaultAnchor();
-  if (!anchor_specifier.IsNamed() && !position_anchor_specifier_ &&
-      !implicit_anchor_) {
+    const AnchorSpecifierValue& anchor_specifier,
+    const ScopedCSSName* position_anchor) const {
+  if (!anchor_specifier.IsNamed() && !position_anchor && !implicit_anchor_) {
     return nullptr;
   }
   const LogicalAnchorQuery* anchor_query = AnchorQuery();
@@ -388,45 +379,29 @@
     return anchor_query->AnchorReference(*query_object_,
                                          &anchor_specifier.GetName());
   }
-  if (anchor_specifier.IsDefault() && position_anchor_specifier_) {
-    return anchor_query->AnchorReference(*query_object_,
-                                         position_anchor_specifier_);
+  if (anchor_specifier.IsDefault() && position_anchor) {
+    return anchor_query->AnchorReference(*query_object_, position_anchor);
   }
   return anchor_query->AnchorReference(*query_object_, implicit_anchor_);
 }
 
-const LayoutObject* AnchorEvaluatorImpl::DefaultAnchor() const {
-  ValidateDefaultAnchor();
-  if (!default_anchor_.has_value()) {
-    const LogicalAnchorReference* reference =
-        ResolveAnchorReference(*AnchorSpecifierValue::Default());
-    default_anchor_ = reference ? reference->layout_object : nullptr;
-  } else {
-#if DCHECK_IS_ON()
-    const LogicalAnchorReference* reference =
-        ResolveAnchorReference(*AnchorSpecifierValue::Default());
-    DCHECK_EQ(*default_anchor_, reference ? reference->layout_object : nullptr);
-#endif
-  }
-  return *default_anchor_;
+const LayoutObject* AnchorEvaluatorImpl::DefaultAnchor(
+    const ScopedCSSName* position_anchor) const {
+  return cached_default_anchor_.Get(position_anchor, [&]() {
+    const LogicalAnchorReference* reference = ResolveAnchorReference(
+        *AnchorSpecifierValue::Default(), position_anchor);
+    return reference ? reference->layout_object : nullptr;
+  });
 }
 
-const PaintLayer* AnchorEvaluatorImpl::DefaultAnchorScrollContainerLayer()
-    const {
-  ValidateDefaultAnchor();
-  if (!default_anchor_scroll_container_layer_.has_value()) {
-    // We won't reach here without a default anchor.
-    default_anchor_scroll_container_layer_ =
-        DefaultAnchor()->ContainingScrollContainerLayer(
-            true /*ignore_layout_view_for_fixed_pos*/);
-  } else {
-#if DCHECK_IS_ON()
-    DCHECK_EQ(*default_anchor_scroll_container_layer_,
-              DefaultAnchor()->ContainingScrollContainerLayer(
-                  true /*ignore_layout_view_for_fixed_pos*/));
-#endif
-  }
-  return *default_anchor_scroll_container_layer_;
+const PaintLayer* AnchorEvaluatorImpl::DefaultAnchorScrollContainerLayer(
+    const ScopedCSSName* position_anchor) const {
+  return cached_default_anchor_scroll_container_layer_.Get(
+      position_anchor, [&]() {
+        return DefaultAnchor(position_anchor)
+            ->ContainingScrollContainerLayer(
+                true /*ignore_layout_view_for_fixed_pos*/);
+      });
 }
 
 bool AnchorEvaluatorImpl::AllowAnchor() const {
@@ -474,46 +449,52 @@
 }
 
 bool AnchorEvaluatorImpl::ShouldUseScrollAdjustmentFor(
-    const LayoutObject* anchor) const {
-  if (!DefaultAnchor()) {
+    const LayoutObject* anchor,
+    const ScopedCSSName* position_anchor) const {
+  if (!DefaultAnchor(position_anchor)) {
     return false;
   }
-  if (anchor == DefaultAnchor()) {
+  if (anchor == DefaultAnchor(position_anchor)) {
     return true;
   }
   return anchor->ContainingScrollContainerLayer(
              true /*ignore_layout_view_for_fixed_pos*/) ==
-         DefaultAnchorScrollContainerLayer();
+         DefaultAnchorScrollContainerLayer(position_anchor);
 }
 
 std::optional<LayoutUnit> AnchorEvaluatorImpl::EvaluateAnchor(
     const AnchorSpecifierValue& anchor_specifier,
     CSSAnchorValue anchor_value,
-    float percentage) const {
+    float percentage,
+    const ScopedCSSName* position_anchor,
+    const std::optional<InsetAreaOffsets>& inset_area_offsets) const {
   if (!AllowAnchor()) {
     return std::nullopt;
   }
 
   const LogicalAnchorReference* anchor_reference =
-      ResolveAnchorReference(anchor_specifier);
+      ResolveAnchorReference(anchor_specifier, position_anchor);
   if (!anchor_reference) {
     return std::nullopt;
   }
 
-  ValidateAppliedInsetAreaOffsets();
+  PhysicalRect inset_area_modified_containing_block_rect =
+      InsetAreaModifiedContainingBlock(inset_area_offsets);
 
   const bool is_y_axis = IsYAxis();
 
   DCHECK(AnchorQuery());
   if (std::optional<LayoutUnit> result = AnchorQuery()->EvaluateAnchor(
-          *anchor_reference, anchor_value, percentage, AvailableSizeAlongAxis(),
+          *anchor_reference, anchor_value, percentage,
+          AvailableSizeAlongAxis(inset_area_modified_containing_block_rect),
           container_converter_, self_writing_direction_,
-          inset_area_modified_containing_block_rect_.offset, is_y_axis,
+          inset_area_modified_containing_block_rect.offset, is_y_axis,
           IsRightOrBottom())) {
     bool& needs_scroll_adjustment = is_y_axis ? needs_scroll_adjustment_in_y_
                                               : needs_scroll_adjustment_in_x_;
     if (!needs_scroll_adjustment &&
-        ShouldUseScrollAdjustmentFor(anchor_reference->layout_object)) {
+        ShouldUseScrollAdjustmentFor(anchor_reference->layout_object,
+                                     position_anchor)) {
       needs_scroll_adjustment = true;
     }
     return result;
@@ -523,13 +504,14 @@
 
 std::optional<LayoutUnit> AnchorEvaluatorImpl::EvaluateAnchorSize(
     const AnchorSpecifierValue& anchor_specifier,
-    CSSAnchorSizeValue anchor_size_value) const {
+    CSSAnchorSizeValue anchor_size_value,
+    const ScopedCSSName* position_anchor) const {
   if (!AllowAnchorSize()) {
     return std::nullopt;
   }
 
   const LogicalAnchorReference* anchor_reference =
-      ResolveAnchorReference(anchor_specifier);
+      ResolveAnchorReference(anchor_specifier, position_anchor);
   if (!anchor_reference) {
     return std::nullopt;
   }
@@ -565,7 +547,6 @@
 
 std::optional<PhysicalOffset> AnchorEvaluatorImpl::ComputeAnchorCenterOffsets(
     const ComputedStyleBuilder& builder) {
-  AnchorScope outer_scope(AnchorScope::Mode::kNone, builder, this);
   // Parameter `percentage` is unused for any non-percentage anchor value.
   const double dummy_percentage = 0;
 
@@ -580,12 +561,14 @@
   {
     AnchorScope anchor_scope(AnchorScope::Mode::kTop, this);
     top = EvaluateAnchor(*AnchorSpecifierValue::Default(),
-                         CSSAnchorValue::kCenter, dummy_percentage);
+                         CSSAnchorValue::kCenter, dummy_percentage,
+                         builder.PositionAnchor(), builder.InsetAreaOffsets());
   }
   {
     AnchorScope anchor_scope(AnchorScope::Mode::kLeft, this);
     left = EvaluateAnchor(*AnchorSpecifierValue::Default(),
-                          CSSAnchorValue::kCenter, dummy_percentage);
+                          CSSAnchorValue::kCenter, dummy_percentage,
+                          builder.PositionAnchor(), builder.InsetAreaOffsets());
   }
   CHECK(top.has_value() == left.has_value());
   if (top.has_value()) {
@@ -600,12 +583,7 @@
     InsetArea inset_area) {
   CHECK(!inset_area.IsNone());
 
-  // Set up the computed position-anchor for the HasDefaultAnchor() call below
-  // to work correctly.
-  AnchorScope outer_scope(AnchorScope::Mode::kNone, std::nullopt,
-                          position_anchor, this);
-
-  if (!HasDefaultAnchor()) {
+  if (!DefaultAnchor(position_anchor)) {
     return std::nullopt;
   }
   InsetArea physical_inset_area = inset_area.ToPhysical(
@@ -625,80 +603,92 @@
   // nothing to do here for nullopt values.
   if (std::optional<blink::AnchorQuery> query = physical_inset_area.UsedTop()) {
     AnchorScope anchor_scope(AnchorScope::Mode::kBaseTop, this);
-    top = Evaluate(query.value()).value_or(LayoutUnit());
+    top = Evaluate(query.value(), position_anchor,
+                   /* inset_area_offsets */ std::nullopt)
+              .value_or(LayoutUnit());
   }
   if (std::optional<blink::AnchorQuery> query =
           physical_inset_area.UsedBottom()) {
     AnchorScope anchor_scope(AnchorScope::Mode::kBaseBottom, this);
-    bottom = Evaluate(query.value()).value_or(LayoutUnit());
+    bottom = Evaluate(query.value(), position_anchor,
+                      /* inset_area_offsets */ std::nullopt)
+                 .value_or(LayoutUnit());
   }
   if (std::optional<blink::AnchorQuery> query =
           physical_inset_area.UsedLeft()) {
     AnchorScope anchor_scope(AnchorScope::Mode::kBaseLeft, this);
-    left = Evaluate(query.value()).value_or(LayoutUnit());
+    left = Evaluate(query.value(), position_anchor,
+                    /* inset_area_offsets */ std::nullopt)
+               .value_or(LayoutUnit());
   }
   if (std::optional<blink::AnchorQuery> query =
           physical_inset_area.UsedRight()) {
     AnchorScope anchor_scope(AnchorScope::Mode::kBaseRight, this);
-    right = Evaluate(query.value()).value_or(LayoutUnit());
+    right = Evaluate(query.value(), position_anchor,
+                     /* inset_area_offsets */ std::nullopt)
+                .value_or(LayoutUnit());
   }
   return InsetAreaOffsets(top, bottom, left, right);
 }
 
-void AnchorEvaluatorImpl::ValidateAppliedInsetAreaOffsets() const {
-  if (GetInsetAreaOffsets() == applied_inset_area_offsets_) {
-    return;
-  }
-  ApplyInsetAreaOffsets();
-}
+PhysicalRect AnchorEvaluatorImpl::InsetAreaModifiedContainingBlock(
+    const std::optional<InsetAreaOffsets>& inset_area_offsets) const {
+  return cached_inset_area_modified_containing_block_.Get(
+      inset_area_offsets, [&]() {
+        if (!inset_area_offsets.has_value()) {
+          return containing_block_rect_;
+        }
 
-void AnchorEvaluatorImpl::ApplyInsetAreaOffsets() const {
-  inset_area_modified_containing_block_rect_ = containing_block_rect_;
-  applied_inset_area_offsets_ = GetInsetAreaOffsets();
-  if (!applied_inset_area_offsets_.has_value()) {
-    return;
-  }
+        PhysicalRect inset_area_modified_containing_block_rect =
+            containing_block_rect_;
 
-  LayoutUnit top = applied_inset_area_offsets_->top_;
-  LayoutUnit bottom = applied_inset_area_offsets_->bottom_;
-  LayoutUnit left = applied_inset_area_offsets_->left_;
-  LayoutUnit right = applied_inset_area_offsets_->right_;
+        LayoutUnit top = inset_area_offsets->top_;
+        LayoutUnit bottom = inset_area_offsets->bottom_;
+        LayoutUnit left = inset_area_offsets->left_;
+        LayoutUnit right = inset_area_offsets->right_;
 
-  // Reduce the container size and adjust the offset based on the inset-area.
-  inset_area_modified_containing_block_rect_.ContractEdges(top, right, bottom,
-                                                           left);
+        // Reduce the container size and adjust the offset based on the
+        // inset-area.
+        inset_area_modified_containing_block_rect.ContractEdges(top, right,
+                                                                bottom, left);
 
-  // For 'center' values (aligned with start and end anchor sides), the
-  // containing block is aligned and sized with the anchor, regardless of
-  // whether it's inside the original containing block or not. Otherwise,
-  // ContractEdges above might have created a negative size if the inset-area is
-  // aligned with an anchor side outside the containing block.
-  if (inset_area_modified_containing_block_rect_.size.width < LayoutUnit()) {
-    DCHECK(left == LayoutUnit() || right == LayoutUnit())
-        << "If aligned to both anchor edges, the size should never be "
-           "negative.";
-    // Collapse the inline size to 0 and align with the single anchor edge
-    // defined by the inset-area.
-    if (left == LayoutUnit()) {
-      DCHECK(right != LayoutUnit());
-      inset_area_modified_containing_block_rect_.offset.left +=
-          inset_area_modified_containing_block_rect_.size.width;
-    }
-    inset_area_modified_containing_block_rect_.size.width = LayoutUnit();
-  }
-  if (inset_area_modified_containing_block_rect_.size.height < LayoutUnit()) {
-    DCHECK(top == LayoutUnit() || bottom == LayoutUnit())
-        << "If aligned to both anchor edges, the size should never be "
-           "negative.";
-    // Collapse the block size to 0 and align with the single anchor edge
-    // defined by the inset-area.
-    if (top == LayoutUnit()) {
-      DCHECK(bottom != LayoutUnit());
-      inset_area_modified_containing_block_rect_.offset.top +=
-          inset_area_modified_containing_block_rect_.size.height;
-    }
-    inset_area_modified_containing_block_rect_.size.height = LayoutUnit();
-  }
+        // For 'center' values (aligned with start and end anchor sides), the
+        // containing block is aligned and sized with the anchor, regardless of
+        // whether it's inside the original containing block or not. Otherwise,
+        // ContractEdges above might have created a negative size if the
+        // inset-area is aligned with an anchor side outside the containing
+        // block.
+        if (inset_area_modified_containing_block_rect.size.width <
+            LayoutUnit()) {
+          DCHECK(left == LayoutUnit() || right == LayoutUnit())
+              << "If aligned to both anchor edges, the size should never be "
+                 "negative.";
+          // Collapse the inline size to 0 and align with the single anchor edge
+          // defined by the inset-area.
+          if (left == LayoutUnit()) {
+            DCHECK(right != LayoutUnit());
+            inset_area_modified_containing_block_rect.offset.left +=
+                inset_area_modified_containing_block_rect.size.width;
+          }
+          inset_area_modified_containing_block_rect.size.width = LayoutUnit();
+        }
+        if (inset_area_modified_containing_block_rect.size.height <
+            LayoutUnit()) {
+          DCHECK(top == LayoutUnit() || bottom == LayoutUnit())
+              << "If aligned to both anchor edges, the size should never be "
+                 "negative.";
+          // Collapse the block size to 0 and align with the single anchor edge
+          // defined by the inset-area.
+          if (top == LayoutUnit()) {
+            DCHECK(bottom != LayoutUnit());
+            inset_area_modified_containing_block_rect.offset.top +=
+                inset_area_modified_containing_block_rect.size.height;
+          }
+          inset_area_modified_containing_block_rect.size.height = LayoutUnit();
+        }
+
+        return inset_area_modified_containing_block_rect;
+      });
 }
 
 void LogicalAnchorReference::Trace(Visitor* visitor) const {
diff --git a/third_party/blink/renderer/core/layout/anchor_evaluator_impl.h b/third_party/blink/renderer/core/layout/anchor_evaluator_impl.h
index 8e2098b..dbeafa6 100644
--- a/third_party/blink/renderer/core/layout/anchor_evaluator_impl.h
+++ b/third_party/blink/renderer/core/layout/anchor_evaluator_impl.h
@@ -269,9 +269,7 @@
         implicit_anchor_(implicit_anchor),
         container_converter_(container_converter),
         self_writing_direction_(self_writing_direction),
-        containing_block_rect_(offset_to_padding_box, available_size),
-        inset_area_modified_containing_block_rect_(offset_to_padding_box,
-                                                   available_size) {
+        containing_block_rect_(offset_to_padding_box, available_size) {
     DCHECK(anchor_query_);
   }
 
@@ -291,9 +289,7 @@
         containing_block_(&containing_block),
         container_converter_(container_converter),
         self_writing_direction_(self_writing_direction),
-        containing_block_rect_(offset_to_padding_box, available_size),
-        inset_area_modified_containing_block_rect_(offset_to_padding_box,
-                                                   available_size) {
+        containing_block_rect_(offset_to_padding_box, available_size) {
     DCHECK(anchor_queries_);
     DCHECK(containing_block_);
   }
@@ -310,7 +306,10 @@
 
   // Evaluates the given anchor query. Returns nullopt if the query invalid
   // (e.g., no target or wrong axis).
-  std::optional<LayoutUnit> Evaluate(const class AnchorQuery&) override;
+  std::optional<LayoutUnit> Evaluate(
+      const class AnchorQuery&,
+      const ScopedCSSName* position_anchor,
+      const std::optional<InsetAreaOffsets>&) override;
 
   std::optional<InsetAreaOffsets> ComputeInsetAreaOffsetsForLayout(
       const ScopedCSSName* position_anchor,
@@ -323,43 +322,48 @@
   // property, or nullopt if there's no such element.
   std::optional<LogicalRect> GetAdditionalFallbackBoundsRect() const;
 
-  bool HasDefaultAnchor() const { return DefaultAnchor() != nullptr; }
-
  private:
   const LogicalAnchorQuery* AnchorQuery() const;
   const LogicalAnchorReference* ResolveAnchorReference(
-      const AnchorSpecifierValue& anchor_specifier) const;
-  bool ShouldUseScrollAdjustmentFor(const LayoutObject* anchor) const;
+      const AnchorSpecifierValue& anchor_specifier,
+      const ScopedCSSName* position_anchor) const;
+  bool ShouldUseScrollAdjustmentFor(const LayoutObject* anchor,
+                                    const ScopedCSSName* position_anchor) const;
 
   std::optional<LayoutUnit> EvaluateAnchor(
       const AnchorSpecifierValue& anchor_specifier,
       CSSAnchorValue anchor_value,
-      float percentage) const;
+      float percentage,
+      const ScopedCSSName* position_anchor,
+      const std::optional<InsetAreaOffsets>&) const;
   std::optional<LayoutUnit> EvaluateAnchorSize(
       const AnchorSpecifierValue& anchor_specifier,
-      CSSAnchorSizeValue anchor_size_value) const;
+      CSSAnchorSizeValue anchor_size_value,
+      const ScopedCSSName* position_anchor) const;
 
-  const LayoutObject* DefaultAnchor() const;
-  const PaintLayer* DefaultAnchorScrollContainerLayer() const;
+  const LayoutObject* DefaultAnchor(const ScopedCSSName* position_anchor) const;
+  const PaintLayer* DefaultAnchorScrollContainerLayer(
+      const ScopedCSSName* position_anchor) const;
 
   bool AllowAnchor() const;
   bool AllowAnchorSize() const;
   bool IsYAxis() const;
   bool IsRightOrBottom() const;
 
-  LayoutUnit AvailableSizeAlongAxis() const {
-    return IsYAxis() ? inset_area_modified_containing_block_rect_.Height()
-                     : inset_area_modified_containing_block_rect_.Width();
+  LayoutUnit AvailableSizeAlongAxis(
+      const PhysicalRect& inset_area_modified_containing_block_rect) const {
+    return IsYAxis() ? inset_area_modified_containing_block_rect.Height()
+                     : inset_area_modified_containing_block_rect.Width();
   }
 
-  void ValidateDefaultAnchor() const;
-  void ValidateAppliedInsetAreaOffsets() const;
-  void ApplyInsetAreaOffsets() const;
+  // Returns the containing block, further constrained by the inset-area.
+  // Not to be confused with the inset-modified containing block.
+  PhysicalRect InsetAreaModifiedContainingBlock(
+      const std::optional<InsetAreaOffsets>&) const;
 
   const LayoutObject* query_object_ = nullptr;
   mutable const LogicalAnchorQuery* anchor_query_ = nullptr;
   const LogicalAnchorQueryMap* anchor_queries_ = nullptr;
-  mutable const ScopedCSSName* position_anchor_specifier_ = nullptr;
   const LayoutObject* implicit_anchor_ = nullptr;
   const LayoutObject* containing_block_ = nullptr;
   const WritingModeConverter container_converter_{
@@ -370,17 +374,51 @@
   // Either width or height will be used, depending on IsYAxis().
   PhysicalRect containing_block_rect_;
 
-  // containing_block_rect_, further constrained by the current inset-area.
-  mutable PhysicalRect inset_area_modified_containing_block_rect_;
+  // A single-value cache. If a call to Get has the same key as the last call,
+  // then the cached result it returned. Otherwise, the value is created using
+  // CreationFunc, then returned.
+  template <typename KeyType, typename ValueType>
+  class CachedValue {
+    STACK_ALLOCATED();
 
-  // The inset-area currently applied to the
-  // inset_area_modified_containing_block_rect_.
-  mutable std::optional<InsetAreaOffsets> applied_inset_area_offsets_;
+    template <typename T>
+    static bool Equals(const T* a, const T* b) {
+      return base::ValuesEquivalent(a, b);
+    }
 
-  // These fields will be populated during `anchor()` evaluation if needed.
-  mutable std::optional<const LayoutObject*> default_anchor_;
-  mutable std::optional<const PaintLayer*>
-      default_anchor_scroll_container_layer_;
+    template <typename T>
+    static bool Equals(const T& a, const T& b) {
+      return a == b;
+    }
+
+   public:
+    template <typename CreationFunc>
+    ValueType Get(KeyType key, CreationFunc create) {
+      if (value_.has_value() && Equals(key_, key)) {
+        DCHECK_EQ(value_.value(), create());
+        return value_.value();
+      }
+      key_ = key;
+      value_ = create();
+      return value_.value();
+    }
+
+   private:
+    KeyType key_{};
+    std::optional<ValueType> value_;
+  };
+
+  // Caches most recent result of InsetAreaModifiedContainingBlock.
+  mutable CachedValue<std::optional<InsetAreaOffsets>, PhysicalRect>
+      cached_inset_area_modified_containing_block_;
+
+  // Caches most recent result of DefaultAnchor.
+  mutable CachedValue<const ScopedCSSName*, const LayoutObject*>
+      cached_default_anchor_;
+
+  // Caches most recent result of DefaultAnchorScrollContainerLayer.
+  mutable CachedValue<const ScopedCSSName*, const PaintLayer*>
+      cached_default_anchor_scroll_container_layer_;
 
   mutable bool needs_scroll_adjustment_in_x_ = false;
   mutable bool needs_scroll_adjustment_in_y_ = false;
diff --git a/third_party/blink/renderer/core/layout/anchor_position_visibility_observer.h b/third_party/blink/renderer/core/layout/anchor_position_visibility_observer.h
index a20a60b..35e8b02 100644
--- a/third_party/blink/renderer/core/layout/anchor_position_visibility_observer.h
+++ b/third_party/blink/renderer/core/layout/anchor_position_visibility_observer.h
@@ -29,7 +29,7 @@
 //    updates with `UpdateForCssAnchorVisibility`. This is needed to ensure we
 //    catch CSS visibility changes on anchor elements.
 //
-// [1] Spec: https://github.com/w3c/csswg-drafts/issues/7758
+// [1] Spec: https://drafts.csswg.org/css-anchor-position-1/#position-visibility
 class AnchorPositionVisibilityObserver final
     : public GarbageCollected<AnchorPositionVisibilityObserver> {
  public:
diff --git a/third_party/blink/renderer/core/layout/inline/inline_cursor.cc b/third_party/blink/renderer/core/layout/inline/inline_cursor.cc
index 11b6d8e..4586772 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_cursor.cc
+++ b/third_party/blink/renderer/core/layout/inline/inline_cursor.cc
@@ -1431,6 +1431,7 @@
     return;
   }
   // |FirstInlineFragmentItemIndex| is 1-based. Convert to 0-based index.
+  DCHECK_GT(item_index, 0UL);
   --item_index;
 
   // Find |FragmentItems| that contains |item_index|.
@@ -1442,6 +1443,7 @@
       if (!Current())
         return;
     }
+    DCHECK_GE(item_index, fragment_items_->SizeOfEarlierFragments());
     item_index -= fragment_items_->SizeOfEarlierFragments();
 #if EXPENSIVE_DCHECKS_ARE_ON()
     InlineCursor check_cursor(*root_block_flow_);
@@ -1453,6 +1455,7 @@
     // If |this| is not rooted at |LayoutBlockFlow|, iterate |FragmentItems|
     // from |LayoutBlockFlow|.
     if (fragment_items_->HasItemIndex(item_index)) {
+      DCHECK_GE(item_index, fragment_items_->SizeOfEarlierFragments());
       item_index -= fragment_items_->SizeOfEarlierFragments();
     } else {
       InlineCursor cursor;
@@ -1464,6 +1467,7 @@
           return;
         }
         if (cursor.fragment_items_ == fragment_items_) {
+          DCHECK_GE(cursor.Current().Item(), fragment_items_->Items().data());
           item_index = base::checked_cast<wtf_size_t>(
               cursor.Current().Item() - fragment_items_->Items().data());
           break;
@@ -1496,6 +1500,7 @@
         MakeNull();
         return;
       }
+      DCHECK_GE(item_index, span_begin_item_index);
       item_index -= span_begin_item_index;
     }
   }
diff --git a/third_party/blink/renderer/core/layout/inline/inline_item_result.cc b/third_party/blink/renderer/core/layout/inline/inline_item_result.cc
index 886bf50..27e8a68 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_item_result.cc
+++ b/third_party/blink/renderer/core/layout/inline/inline_item_result.cc
@@ -67,10 +67,12 @@
   visitor->Trace(positioned_float);
 }
 
-String InlineItemResult::ToString(const String& ifc_text_content) const {
-  // This is almost same as InlineItem::ToString(), but this shows associated
-  // text precisely.
+String InlineItemResult::ToString(const String& ifc_text_content,
+                                  const String& indent) const {
+  // Unlike InlineItem::ToString(), this shows associated text precisely, and
+  // shows kOpenRubyColumn structure.
   StringBuilder builder;
+  builder.Append(indent);
   builder.Append("InlineItemResult ");
   builder.Append(item->InlineItemTypeToString(item->Type()));
   builder.Append(" ");
@@ -78,6 +80,26 @@
     builder.Append(
         ifc_text_content.Substring(TextOffset().start, TextOffset().Length())
             .EncodeForDebugging());
+  } else if (item->Type() == InlineItem::kOpenRubyColumn && ruby_column) {
+    builder.Append(item->GetLayoutObject()->ToString());
+    builder.Append(", base_line: [\n");
+    String child_indent = indent + "\t";
+    for (const auto& r : ruby_column->base_line.Results()) {
+      builder.Append(r.ToString(ifc_text_content, child_indent));
+      builder.Append("\n");
+    }
+    for (wtf_size_t i = 0; i < ruby_column->annotation_line_list.size(); ++i) {
+      builder.Append(indent);
+      builder.Append("], annotation_line_list[");
+      builder.AppendNumber(i);
+      builder.Append("]: [\n");
+      for (const auto& r : ruby_column->annotation_line_list[i].Results()) {
+        builder.Append(r.ToString(ifc_text_content, child_indent));
+        builder.Append("\n");
+      }
+    }
+    builder.Append(indent);
+    builder.Append("]");
   } else if (item->GetLayoutObject()) {
     builder.Append(item->GetLayoutObject()->ToString());
   }
diff --git a/third_party/blink/renderer/core/layout/inline/inline_item_result.h b/third_party/blink/renderer/core/layout/inline/inline_item_result.h
index 8344ca5..0b5683d 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_item_result.h
+++ b/third_party/blink/renderer/core/layout/inline/inline_item_result.h
@@ -90,7 +90,10 @@
 #if DCHECK_IS_ON()
   void CheckConsistency(bool allow_null_shape_result = false) const;
 #endif
-  String ToString(const String& ifc_text_content) const;
+  // `indent` is prepended to the content. If the content consists of multiple
+  // lines, `indent` is prepended to each of lines.
+  String ToString(const String& ifc_text_content,
+                  const String& indent = "") const;
 
   // The InlineItem and its index.
   const InlineItem* item = nullptr;
diff --git a/third_party/blink/renderer/core/layout/inline/line_breaker.cc b/third_party/blink/renderer/core/layout/inline/line_breaker.cc
index 8eaf7dd..543c7cb5 100644
--- a/third_party/blink/renderer/core/layout/inline/line_breaker.cc
+++ b/third_party/blink/renderer/core/layout/inline/line_breaker.cc
@@ -1165,17 +1165,19 @@
   if (UNLIKELY(HasHyphen()))
     position_ -= RemoveHyphen(line_info->MutableResults());
 
+  // Try to commit |pending_end_overhang_| of a prior InlineItemResult.
+  // |pending_end_overhang_| doesn't work well with bidi reordering. It's
+  // difficult to compute overhang after bidi reordering because it affect
+  // line breaking.
+  if (maybe_have_end_overhang_) {
+    position_ -= CommitPendingEndOverhang(item, line_info);
+  }
+
   InlineItemResult* item_result = nullptr;
   if (!is_svg_text_) {
     item_result = AddItem(item, line_info);
     item_result->should_create_line_box = true;
   }
-  // Try to commit |pending_end_overhang_| of a prior InlineItemResult.
-  // |pending_end_overhang_| doesn't work well with bidi reordering. It's
-  // difficult to compute overhang after bidi reordering because it affect
-  // line breaking.
-  if (maybe_have_end_overhang_)
-    position_ -= CommitPendingEndOverhang(line_info);
 
   if (auto_wrap_) {
     if (mode_ == LineBreakerMode::kMinContent &&
diff --git a/third_party/blink/renderer/core/layout/inline/line_info.cc b/third_party/blink/renderer/core/layout/inline/line_info.cc
index 0815baa0..2f37660 100644
--- a/third_party/blink/renderer/core/layout/inline/line_info.cc
+++ b/third_party/blink/renderer/core/layout/inline/line_info.cc
@@ -536,7 +536,7 @@
           << " width_=" << line_info.Width() << " Results=[\n";
   const String& text_content = line_info.ItemsData().text_content;
   for (const auto& result : line_info.Results()) {
-    ostream << "\t" << result.ToString(text_content).Utf8().c_str() << "\n";
+    ostream << result.ToString(text_content, "\t").Utf8() << "\n";
   }
   return ostream << "]";
 }
diff --git a/third_party/blink/renderer/core/layout/inline/ruby_utils.cc b/third_party/blink/renderer/core/layout/inline/ruby_utils.cc
index dcd3224..9cec36e 100644
--- a/third_party/blink/renderer/core/layout/inline/ruby_utils.cc
+++ b/third_party/blink/renderer/core/layout/inline/ruby_utils.cc
@@ -223,17 +223,18 @@
   return true;
 }
 
-LayoutUnit CommitPendingEndOverhang(LineInfo* line_info) {
+LayoutUnit CommitPendingEndOverhang(const InlineItem& text_item,
+                                    LineInfo* line_info) {
   DCHECK(line_info);
   InlineItemResults* items = line_info->MutableResults();
-  if (items->size() < 2U)
-    return LayoutUnit();
-  const InlineItemResult& text_item = items->back();
-  if (text_item.item->Type() == InlineItem::kControl) {
+  if (items->size() < 1U) {
     return LayoutUnit();
   }
-  DCHECK(text_item.item->Type() == InlineItem::kText);
-  wtf_size_t i = items->size() - 2;
+  if (text_item.Type() == InlineItem::kControl) {
+    return LayoutUnit();
+  }
+  DCHECK_EQ(text_item.Type(), InlineItem::kText);
+  wtf_size_t i = items->size() - 1;
   while ((*items)[i].item->Type() != InlineItem::kAtomicInline) {
     const auto type = (*items)[i].item->Type();
     if (type != InlineItem::kOpenTag && type != InlineItem::kCloseTag) {
@@ -249,15 +250,16 @@
   if (atomic_inline_item.pending_end_overhang <= LayoutUnit())
     return LayoutUnit();
   if (atomic_inline_item.item->Style()->FontSize() <
-      text_item.item->Style()->FontSize())
+      text_item.Style()->FontSize()) {
     return LayoutUnit();
+  }
   // Ideally we should refer to inline_size of |text_item| instead of the width
   // of the InlineItem's ShapeResult. However it's impossible to compute
   // inline_size of |text_item| before calling BreakText(), and BreakText()
   // requires precise |position_| which takes |end_overhang| into account.
   LayoutUnit end_overhang =
       std::min(atomic_inline_item.pending_end_overhang,
-               LayoutUnit(text_item.item->TextShapeResult()->Width()));
+               LayoutUnit(text_item.TextShapeResult()->Width()));
   DCHECK_EQ(atomic_inline_item.margins.inline_end, LayoutUnit());
   atomic_inline_item.margins.inline_end = -end_overhang;
   atomic_inline_item.inline_size -= end_overhang;
diff --git a/third_party/blink/renderer/core/layout/inline/ruby_utils.h b/third_party/blink/renderer/core/layout/inline/ruby_utils.h
index 67e6f7fa..41ae83d 100644
--- a/third_party/blink/renderer/core/layout/inline/ruby_utils.h
+++ b/third_party/blink/renderer/core/layout/inline/ruby_utils.h
@@ -69,12 +69,13 @@
 bool CanApplyStartOverhang(const LineInfo& line_info,
                            LayoutUnit& start_overhang);
 
-// This should be called after InlineItemResult for a text is added in
+// This should be called before a text `InlineItem` is added in
 // LineBreaker::HandleText().
 //
 // This function may update a InlineItemResult representing RubyColumn
 // in |line_info|
-LayoutUnit CommitPendingEndOverhang(LineInfo* line_info);
+LayoutUnit CommitPendingEndOverhang(const InlineItem& text_item,
+                                    LineInfo* line_info);
 
 // Stores ComputeAnnotationOverflow() results.
 //
diff --git a/third_party/blink/renderer/core/layout/layout_theme_linux.cc b/third_party/blink/renderer/core/layout/layout_theme_linux.cc
index f335ed9..463cb71e 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_linux.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme_linux.cc
@@ -19,8 +19,14 @@
 }
 
 String LayoutThemeLinux::ExtraDefaultStyleSheet() {
-  return LayoutThemeDefault::ExtraDefaultStyleSheet() +
-         UncompressResourceAsASCIIString(IDR_UASTYLE_THEME_CHROMIUM_LINUX_CSS);
+  String stylesheet =
+      LayoutThemeDefault::ExtraDefaultStyleSheet() +
+      UncompressResourceAsASCIIString(IDR_UASTYLE_THEME_CHROMIUM_LINUX_CSS);
+  if (RuntimeEnabledFeatures::StylableSelectEnabled()) {
+    stylesheet = stylesheet + UncompressResourceAsASCIIString(
+                                  IDR_UASTYLE_STYLABLE_SELECT_LINUX_CSS);
+  }
+  return stylesheet;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
index a71c364..9429ebe 100644
--- a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
@@ -308,11 +308,12 @@
 }
 
 // Updates `node`'s associated `PaintLayer` for `position-visibility`. See:
-// https://github.com/w3c/csswg-drafts/issues/7758. The values of `no-overflow`
-// and `anchors-valid` are computed and directly update the `PaintLayer` in this
-// function. The remaining value of `anchors-visible` is computed via an
-// intersection observer set up in this function, and the `PaintLayer` is
-// updated later during the post-layout intersection observer step.
+// https://drafts.csswg.org/css-anchor-position-1/#position-visibility. The
+// values of `no-overflow` and `anchors-valid` are computed and directly update
+// the `PaintLayer` in this function. The remaining value of `anchors-visible`
+// is computed via an intersection observer set up in this function, and the
+// `PaintLayer` is updated later during the post-layout intersection observer
+// step.
 void UpdatePositionVisibilityAfterLayout(
     const OutOfFlowLayoutPart::OffsetInfo& offset_info,
     const BlockNode& node,
diff --git a/third_party/blink/renderer/core/loader/build.gni b/third_party/blink/renderer/core/loader/build.gni
index 8c6e16aab..9b7facf 100644
--- a/third_party/blink/renderer/core/loader/build.gni
+++ b/third_party/blink/renderer/core/loader/build.gni
@@ -129,8 +129,6 @@
   "resource/script_resource.h",
   "resource/speculation_rules_resource.cc",
   "resource/speculation_rules_resource.h",
-  "resource/svg_document_resource.cc",
-  "resource/svg_document_resource.h",
   "resource/text_resource.cc",
   "resource/text_resource.h",
   "resource/video_timing.h",
diff --git a/third_party/blink/renderer/core/loader/resource/svg_document_resource.cc b/third_party/blink/renderer/core/loader/resource/svg_document_resource.cc
deleted file mode 100644
index cb9a072..0000000
--- a/third_party/blink/renderer/core/loader/resource/svg_document_resource.cc
+++ /dev/null
@@ -1,109 +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 "third_party/blink/renderer/core/loader/resource/svg_document_resource.h"
-
-#include "third_party/blink/renderer/core/svg/svg_resource_document_content.h"
-#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
-
-namespace blink {
-
-namespace {
-
-class SVGDocumentResourceFactory : public ResourceFactory {
- public:
-  SVGDocumentResourceFactory(
-      ExecutionContext* execution_context,
-      scoped_refptr<base::SingleThreadTaskRunner> task_runner)
-      : ResourceFactory(ResourceType::kSVGDocument,
-                        TextResourceDecoderOptions::kXMLContent),
-        context_(execution_context),
-        task_runner_(std::move(task_runner)) {}
-
-  Resource* Create(
-      const ResourceRequest& request,
-      const ResourceLoaderOptions& options,
-      const TextResourceDecoderOptions& decoder_options) const override {
-    auto* content = MakeGarbageCollected<SVGResourceDocumentContent>(
-        context_, task_runner_);
-    return MakeGarbageCollected<SVGDocumentResource>(request, options,
-                                                     decoder_options, content);
-  }
-
- private:
-  ExecutionContext* context_;
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-};
-
-bool MimeTypeAllowed(const ResourceResponse& response) {
-  AtomicString mime_type = response.MimeType();
-  if (response.IsHTTP()) {
-    mime_type = response.HttpContentType();
-  }
-  return mime_type == "image/svg+xml" || mime_type == "text/xml" ||
-         mime_type == "application/xml" || mime_type == "application/xhtml+xml";
-}
-
-}  // namespace
-
-SVGDocumentResource* SVGDocumentResource::Fetch(
-    FetchParameters& params,
-    ResourceFetcher* fetcher,
-    ExecutionContext* execution_context) {
-  return To<SVGDocumentResource>(fetcher->RequestResource(
-      params,
-      SVGDocumentResourceFactory(execution_context, fetcher->GetTaskRunner()),
-      nullptr));
-}
-
-SVGDocumentResource::SVGDocumentResource(
-    const ResourceRequest& request,
-    const ResourceLoaderOptions& options,
-    const TextResourceDecoderOptions& decoder_options,
-    SVGResourceDocumentContent* content)
-    : TextResource(request,
-                   ResourceType::kSVGDocument,
-                   options,
-                   decoder_options),
-      content_(content) {}
-
-void SVGDocumentResource::NotifyStartLoad() {
-  TextResource::NotifyStartLoad();
-  CHECK_EQ(GetStatus(), ResourceStatus::kPending);
-  content_->NotifyStartLoad();
-}
-
-void SVGDocumentResource::Finish(base::TimeTicks load_finish_time,
-                                 base::SingleThreadTaskRunner* task_runner) {
-  const ResourceResponse& response = GetResponse();
-  if (MimeTypeAllowed(response)) {
-    content_->UpdateDocument(DecodedText(), response.CurrentRequestUrl());
-  } else if (!ErrorOccurred()) {
-    SetStatus(ResourceStatus::kDecodeError);
-    ClearData();
-  }
-  content_->UpdateStatus(GetStatus());
-  TextResource::Finish(load_finish_time, task_runner);
-  content_->NotifyObservers();
-}
-
-void SVGDocumentResource::FinishAsError(
-    const ResourceError& error,
-    base::SingleThreadTaskRunner* task_runner) {
-  TextResource::FinishAsError(error, task_runner);
-  content_->ClearDocument();
-  content_->UpdateStatus(GetStatus());
-  content_->NotifyObservers();
-}
-
-void SVGDocumentResource::DestroyDecodedDataForFailedRevalidation() {
-  content_->ClearDocument();
-}
-
-void SVGDocumentResource::Trace(Visitor* visitor) const {
-  visitor->Trace(content_);
-  TextResource::Trace(visitor);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/resource/svg_document_resource.h b/third_party/blink/renderer/core/loader/resource/svg_document_resource.h
deleted file mode 100644
index 8f40187b..0000000
--- a/third_party/blink/renderer/core/loader/resource/svg_document_resource.h
+++ /dev/null
@@ -1,53 +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 THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_SVG_DOCUMENT_RESOURCE_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_SVG_DOCUMENT_RESOURCE_H_
-
-#include "third_party/blink/renderer/core/loader/resource/text_resource.h"
-#include "third_party/blink/renderer/platform/heap/member.h"
-
-namespace blink {
-
-class FetchParameters;
-class ResourceFetcher;
-class SVGResourceDocumentContent;
-
-class SVGDocumentResource final : public TextResource {
- public:
-  static SVGDocumentResource* Fetch(FetchParameters&,
-                                    ResourceFetcher*,
-                                    ExecutionContext*);
-
-  SVGDocumentResource(const ResourceRequest&,
-                      const ResourceLoaderOptions&,
-                      const TextResourceDecoderOptions&,
-                      SVGResourceDocumentContent*);
-
-  void NotifyStartLoad() override;
-  void Finish(base::TimeTicks finish_time,
-              base::SingleThreadTaskRunner*) override;
-  void FinishAsError(const ResourceError&,
-                     base::SingleThreadTaskRunner*) override;
-
-  SVGResourceDocumentContent* GetContent() const { return content_.Get(); }
-
-  void Trace(Visitor*) const override;
-
- private:
-  void DestroyDecodedDataForFailedRevalidation() override;
-
-  Member<SVGResourceDocumentContent> content_;
-};
-
-template <>
-struct DowncastTraits<SVGDocumentResource> {
-  static bool AllowFrom(const Resource& resource) {
-    return resource.GetType() == ResourceType::kSVGDocument;
-  }
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_SVG_DOCUMENT_RESOURCE_H_
diff --git a/third_party/blink/renderer/core/loader/resource/text_resource.cc b/third_party/blink/renderer/core/loader/resource/text_resource.cc
index 9b5508b7..9c7f603 100644
--- a/third_party/blink/renderer/core/loader/resource/text_resource.cc
+++ b/third_party/blink/renderer/core/loader/resource/text_resource.cc
@@ -5,12 +5,39 @@
 #include "third_party/blink/renderer/core/loader/resource/text_resource.h"
 
 #include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h"
 #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
 namespace blink {
 
+namespace {
+
+class SVGDocumentResourceFactory : public ResourceFactory {
+ public:
+  SVGDocumentResourceFactory()
+      : ResourceFactory(ResourceType::kSVGDocument,
+                        TextResourceDecoderOptions::kXMLContent) {}
+
+  Resource* Create(
+      const ResourceRequest& request,
+      const ResourceLoaderOptions& options,
+      const TextResourceDecoderOptions& decoder_options) const override {
+    return MakeGarbageCollected<TextResource>(
+        request, ResourceType::kSVGDocument, options, decoder_options);
+  }
+};
+
+}  // namespace
+
+TextResource* TextResource::FetchSVGDocument(FetchParameters& params,
+                                             ResourceFetcher* fetcher,
+                                             ResourceClient* client) {
+  return To<TextResource>(
+      fetcher->RequestResource(params, SVGDocumentResourceFactory(), client));
+}
+
 TextResource::TextResource(const ResourceRequest& resource_request,
                            ResourceType type,
                            const ResourceLoaderOptions& options,
diff --git a/third_party/blink/renderer/core/loader/resource/text_resource.h b/third_party/blink/renderer/core/loader/resource/text_resource.h
index 8f1f910..2a2f9169 100644
--- a/third_party/blink/renderer/core/loader/resource/text_resource.h
+++ b/third_party/blink/renderer/core/loader/resource/text_resource.h
@@ -14,8 +14,13 @@
 
 namespace blink {
 
+class ResourceFetcher;
+
 class CORE_EXPORT TextResource : public Resource {
  public:
+  static TextResource* FetchSVGDocument(FetchParameters&,
+                                        ResourceFetcher*,
+                                        ResourceClient*);
   TextResource(const ResourceRequest&,
                ResourceType,
                const ResourceLoaderOptions&,
diff --git a/third_party/blink/renderer/core/paint/highlight_overlay.cc b/third_party/blink/renderer/core/paint/highlight_overlay.cc
index 4471fbf..e9507bb 100644
--- a/third_party/blink/renderer/core/paint/highlight_overlay.cc
+++ b/third_party/blink/renderer/core/paint/highlight_overlay.cc
@@ -519,7 +519,7 @@
   return result;
 }
 
-Vector<HighlightPart> HighlightOverlay::ComputeParts(
+HeapVector<HighlightPart> HighlightOverlay::ComputeParts(
     const TextFragmentPaintInfo& content_offsets,
     const HeapVector<HighlightLayer>& layers,
     const Vector<HighlightEdge>& edges) {
@@ -531,7 +531,7 @@
       {content_offsets.from, content_offsets.to},
       originating_text_style.text_decoration_color};
 
-  Vector<HighlightPart> result{};
+  HeapVector<HighlightPart> result;
   Vector<std::optional<HighlightRange>> active(layers.size());
   std::optional<unsigned> prev_offset{};
   if (edges.empty()) {
diff --git a/third_party/blink/renderer/core/paint/highlight_overlay.h b/third_party/blink/renderer/core/paint/highlight_overlay.h
index a08f0fb6..cc906a70 100644
--- a/third_party/blink/renderer/core/paint/highlight_overlay.h
+++ b/third_party/blink/renderer/core/paint/highlight_overlay.h
@@ -44,7 +44,10 @@
     explicit HighlightLayer(HighlightLayerType type,
                             const AtomicString& name = g_null_atom);
 
-    void Trace(Visitor* visitor) const { visitor->Trace(style); }
+    void Trace(Visitor* visitor) const {
+      visitor->Trace(style);
+      visitor->Trace(text_style);
+    }
 
     String ToString() const;
     enum PseudoId PseudoId() const;
@@ -160,6 +163,8 @@
                   Vector<HighlightDecoration>);
     HighlightPart(HighlightLayerType, uint16_t, HighlightRange);
 
+    void Trace(Visitor* visitor) const { visitor->Trace(style); }
+
     String ToString() const;
 
     bool operator==(const HighlightPart&) const;
@@ -205,7 +210,7 @@
   //
   // The edges must not represent overlapping ranges. If the highlight is active
   // in overlapping ranges, those ranges must be merged before ComputeEdges.
-  static Vector<HighlightPart> ComputeParts(
+  static HeapVector<HighlightPart> ComputeParts(
       const TextFragmentPaintInfo& originating,
       const HeapVector<HighlightLayer>& layers,
       const Vector<HighlightEdge>& edges);
@@ -222,5 +227,7 @@
 
 WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS(
     blink::HighlightOverlay::HighlightLayer)
+WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS(
+    blink::HighlightOverlay::HighlightPart)
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_HIGHLIGHT_OVERLAY_H_
diff --git a/third_party/blink/renderer/core/paint/highlight_overlay_test.cc b/third_party/blink/renderer/core/paint/highlight_overlay_test.cc
index 92a4ede..0baf42d 100644
--- a/third_party/blink/renderer/core/paint/highlight_overlay_test.cc
+++ b/third_party/blink/renderer/core/paint/highlight_overlay_test.cc
@@ -421,7 +421,7 @@
 
   // clang-format off
   EXPECT_EQ(HighlightOverlay::ComputeParts(originating, layers, edges),
-            (Vector<HighlightPart>{
+            (HeapVector<HighlightPart>{
                 HighlightPart{HighlightLayerType::kOriginating, 0, {0,25}, originating_text_style.style,
                               {{HighlightLayerType::kOriginating, 0, {0,25}, originating_color}}},
             }))
@@ -441,7 +441,7 @@
       *grammar, *spelling, *target);
 
   EXPECT_EQ(HighlightOverlay::ComputeParts(originating, layers, edges2),
-            (Vector<HighlightPart>{
+            (HeapVector<HighlightPart>{
                 HighlightPart{HighlightLayerType::kCustom, 1, {0,6}, foo_text_style.style,
                               {{HighlightLayerType::kOriginating, 0, {0,25}, originating_color},
                                {HighlightLayerType::kCustom, 1, {0,14}, foo_color}}},
@@ -502,7 +502,7 @@
       *grammar, *spelling, *target);
 
   EXPECT_EQ(HighlightOverlay::ComputeParts(originating, layers, edges3),
-            (Vector<HighlightPart>{
+            (HeapVector<HighlightPart>{
                 HighlightPart{HighlightLayerType::kOriginating, 0, {0,6}, originating_text_style.style,
                               {{HighlightLayerType::kOriginating, 0, {0,25}, originating_color}}},
                 HighlightPart{HighlightLayerType::kSpelling, 3, {6,9}, spelling_text_style.style,
@@ -560,7 +560,7 @@
       *grammar, *spelling, *target);
 
   EXPECT_EQ(HighlightOverlay::ComputeParts(originating2, layers, edges4),
-            (Vector<HighlightPart>{
+            (HeapVector<HighlightPart>{
                 HighlightPart{HighlightLayerType::kSpelling, 3, {8,9}, spelling_text_style.style,
                               {{HighlightLayerType::kOriginating, 0, {8,18}, originating_color},
                                {HighlightLayerType::kCustom, 1, {8,14}, foo_color},
@@ -605,7 +605,7 @@
       *spelling, *none);
 
   EXPECT_EQ(HighlightOverlay::ComputeParts(originating2, layers, edges5),
-            (Vector<HighlightPart>{
+            (HeapVector<HighlightPart>{
                 HighlightPart{HighlightLayerType::kSpelling, 3, {8,9}, spelling_text_style.style,
                               {{HighlightLayerType::kOriginating, 0, {8,18}, originating_color},
                                {HighlightLayerType::kSpelling, 3, {8,9}, spelling_color}}},
@@ -635,7 +635,7 @@
       *grammar, *spelling, *target);
 
   EXPECT_EQ(HighlightOverlay::ComputeParts(originating3, layers, edges6),
-            (Vector<HighlightPart>{
+            (HeapVector<HighlightPart>{
                 HighlightPart{HighlightLayerType::kOriginating, 0, {1,4}, originating_text_style.style,
                               {{HighlightLayerType::kOriginating, 0, {1,4}, originating_color}}},
             }))
@@ -657,7 +657,7 @@
       *grammar, *spelling, *target);
 
   EXPECT_EQ(HighlightOverlay::ComputeParts(originating4, layers, edges7),
-            (Vector<HighlightPart>{
+            (HeapVector<HighlightPart>{
                 HighlightPart{HighlightLayerType::kOriginating, 0, {25,28}, originating_text_style.style,
                               {{HighlightLayerType::kOriginating, 0, {25,28}, originating_color}}},
             }))
diff --git a/third_party/blink/renderer/core/paint/highlight_painter.h b/third_party/blink/renderer/core/paint/highlight_painter.h
index a454f2d0..cb8f75a 100644
--- a/third_party/blink/renderer/core/paint/highlight_painter.h
+++ b/third_party/blink/renderer/core/paint/highlight_painter.h
@@ -284,7 +284,7 @@
   DocumentMarkerVector grammar_;
   DocumentMarkerVector custom_;
   HeapVector<HighlightLayer> layers_;
-  Vector<HighlightPart> parts_;
+  HeapVector<HighlightPart> parts_;
   Vector<HighlightEdgeInfo> edges_info_;
   Case paint_case_;
 };
diff --git a/third_party/blink/renderer/core/paint/text_paint_style.h b/third_party/blink/renderer/core/paint/text_paint_style.h
index b9bdc2e..bd5077e 100644
--- a/third_party/blink/renderer/core/paint/text_paint_style.h
+++ b/third_party/blink/renderer/core/paint/text_paint_style.h
@@ -11,6 +11,8 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
+#include "third_party/blink/renderer/platform/heap/forward.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
@@ -27,11 +29,13 @@
   Color emphasis_mark_color;
   float stroke_width;
   mojom::blink::ColorScheme color_scheme;
-  scoped_refptr<const ShadowList> shadow;
+  Member<const ShadowList> shadow;
   TextDecorationLine selection_decoration_lines;
   Color selection_decoration_color;
   EPaintOrder paint_order;
 
+  void Trace(Visitor* visitor) const { visitor->Trace(shadow); }
+
   bool operator==(const TextPaintStyle& other) const {
     return current_color == other.current_color &&
            fill_color == other.fill_color &&
diff --git a/third_party/blink/renderer/core/paint/text_painter.cc b/third_party/blink/renderer/core/paint/text_painter.cc
index a8e313d..ae5c18ea 100644
--- a/third_party/blink/renderer/core/paint/text_painter.cc
+++ b/third_party/blink/renderer/core/paint/text_painter.cc
@@ -150,7 +150,7 @@
     if (text_style.shadow || shadow_mode == TextPainter::kShadowsOnly) {
       state_saver.SaveIfNeeded();
       context.SetDrawLooper(CreateDrawLooper(
-          text_style.shadow.get(), DrawLooperBuilder::kShadowIgnoresAlpha,
+          text_style.shadow.Get(), DrawLooperBuilder::kShadowIgnoresAlpha,
           text_style.current_color, text_style.color_scheme, shadow_mode));
     }
   }
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index 7b0e4f2..8e139c2 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -2711,19 +2711,17 @@
 }
 
 std::optional<blink::Color> ComputedStyle::ScrollbarThumbColorResolved() const {
-  const std::optional<StyleScrollbarColor>& scrollbar_color = ScrollbarColor();
-  if (scrollbar_color.has_value()) {
-    return scrollbar_color.value().GetThumbColor().Resolve(GetCurrentColor(),
-                                                           UsedColorScheme());
+  if (const StyleScrollbarColor* scrollbar_color = ScrollbarColor()) {
+    return scrollbar_color->GetThumbColor().Resolve(GetCurrentColor(),
+                                                    UsedColorScheme());
   }
   return std::nullopt;
 }
 
 std::optional<blink::Color> ComputedStyle::ScrollbarTrackColorResolved() const {
-  const std::optional<StyleScrollbarColor>& scrollbar_color = ScrollbarColor();
-  if (scrollbar_color.has_value()) {
-    return scrollbar_color.value().GetTrackColor().Resolve(GetCurrentColor(),
-                                                           UsedColorScheme());
+  if (const StyleScrollbarColor* scrollbar_color = ScrollbarColor()) {
+    return scrollbar_color->GetTrackColor().Resolve(GetCurrentColor(),
+                                                    UsedColorScheme());
   }
   return std::nullopt;
 }
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index 3484ec9..337b5e35 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -707,8 +707,7 @@
   }
 
   bool UsesStandardScrollbarStyle() const {
-    return ScrollbarWidth() != EScrollbarWidth::kAuto ||
-           ScrollbarColor().has_value();
+    return ScrollbarWidth() != EScrollbarWidth::kAuto || ScrollbarColor();
   }
 
   bool HasCustomScrollbarStyle(const Document& document) const;
@@ -2762,6 +2761,9 @@
   bool HasEffectiveAppearance() const {
     return ComputedStyle::HasEffectiveAppearance(EffectiveAppearance());
   }
+  bool HasBaseSelectAppearance() const {
+    return Appearance() == ControlPart::kBaseSelectPart;
+  }
 
   // backdrop-filter
   FilterOperations::FilterOperationVector& MutableBackdropFilterOperations() {
diff --git a/third_party/blink/renderer/core/style/filter_operation.h b/third_party/blink/renderer/core/style/filter_operation.h
index c84cd1e..2ebd7a6 100644
--- a/third_party/blink/renderer/core/style/filter_operation.h
+++ b/third_party/blink/renderer/core/style/filter_operation.h
@@ -364,6 +364,11 @@
   explicit DropShadowFilterOperation(const ShadowData& shadow)
       : FilterOperation(OperationType::kDropShadow), shadow_(shadow) {}
 
+  void Trace(Visitor* visitor) const override {
+    visitor->Trace(shadow_);
+    FilterOperation::Trace(visitor);
+  }
+
   const ShadowData& Shadow() const { return shadow_; }
 
   bool AffectsOpacity() const override { return true; }
diff --git a/third_party/blink/renderer/core/style/shadow_data.h b/third_party/blink/renderer/core/style/shadow_data.h
index 083d52d..3ea6f68 100644
--- a/third_party/blink/renderer/core/style/shadow_data.h
+++ b/third_party/blink/renderer/core/style/shadow_data.h
@@ -39,7 +39,7 @@
 // This class holds information about shadows for the text-shadow and box-shadow
 // properties, as well as the drop-shadow(...) filter operation.
 class CORE_EXPORT ShadowData {
-  USING_FAST_MALLOC(ShadowData);
+  DISALLOW_NEW();
 
  public:
   ShadowData(gfx::Vector2dF offset,
@@ -68,6 +68,8 @@
         style_(style),
         opacity_(opacity) {}
 
+  void Trace(Visitor* visitor) const { visitor->Trace(color_); }
+
   bool operator==(const ShadowData&) const = default;
 
   static ShadowData NeutralValue();
@@ -97,4 +99,6 @@
 
 }  // namespace blink
 
+WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS(blink::ShadowData)
+
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_STYLE_SHADOW_DATA_H_
diff --git a/third_party/blink/renderer/core/style/shadow_list.h b/third_party/blink/renderer/core/style/shadow_list.h
index df08cbc..3cbddd7c 100644
--- a/third_party/blink/renderer/core/style/shadow_list.h
+++ b/third_party/blink/renderer/core/style/shadow_list.h
@@ -44,18 +44,19 @@
 
 namespace blink {
 
-typedef Vector<ShadowData, 1> ShadowDataVector;
+typedef HeapVector<ShadowData, 1> ShadowDataVector;
 
 // These are used to store shadows in specified order, but we usually want to
 // iterate over them backwards as the first-specified shadow is painted on top.
-class ShadowList : public RefCounted<ShadowList> {
-  USING_FAST_MALLOC(ShadowList);
-
+class ShadowList : public GarbageCollected<ShadowList> {
  public:
-  // This consumes passed in vector.
-  static scoped_refptr<ShadowList> Adopt(ShadowDataVector& shadows) {
-    return base::AdoptRef(new ShadowList(shadows));
+  explicit ShadowList(ShadowDataVector&& shadows) : shadows_(shadows) {
+    // If we have no shadows, we use a null ShadowList
+    DCHECK(!shadows.empty());
   }
+
+  void Trace(Visitor* visitor) const { visitor->Trace(shadows_); }
+
   const ShadowDataVector& Shadows() const { return shadows_; }
   bool operator==(const ShadowList& o) const { return shadows_ == o.shadows_; }
   bool operator!=(const ShadowList& o) const { return !(*this == o); }
@@ -67,12 +68,6 @@
   void AdjustRectForShadow(gfx::RectF&) const;
 
  private:
-  ShadowList(ShadowDataVector& shadows) {
-    // If we have no shadows, we use a null ShadowList
-    DCHECK(!shadows.empty());
-    shadows_.swap(shadows);
-    shadows_.shrink_to_fit();
-  }
   ShadowDataVector shadows_;
 };
 
diff --git a/third_party/blink/renderer/core/style/style_pending_image.h b/third_party/blink/renderer/core/style/style_pending_image.h
index dc95059..4bda1f5 100644
--- a/third_party/blink/renderer/core/style/style_pending_image.h
+++ b/third_party/blink/renderer/core/style/style_pending_image.h
@@ -77,7 +77,7 @@
                                 const Document&,
                                 const ComputedStyle&,
                                 const gfx::SizeF& target_size) const override {
-    NOTREACHED();
+    DUMP_WILL_BE_NOTREACHED_NORETURN();
     return nullptr;
   }
   bool KnownToBeOpaque(const Document&, const ComputedStyle&) const override {
diff --git a/third_party/blink/renderer/core/style/style_scrollbar_color.h b/third_party/blink/renderer/core/style/style_scrollbar_color.h
index 13374d9..2f45978 100644
--- a/third_party/blink/renderer/core/style/style_scrollbar_color.h
+++ b/third_party/blink/renderer/core/style/style_scrollbar_color.h
@@ -11,13 +11,16 @@
 
 namespace blink {
 
-class CORE_EXPORT StyleScrollbarColor {
-  DISALLOW_NEW();
-
+class CORE_EXPORT StyleScrollbarColor
+    : public GarbageCollected<StyleScrollbarColor> {
  public:
-  StyleScrollbarColor();
   StyleScrollbarColor(StyleColor thumb_color, StyleColor track_color);
 
+  void Trace(Visitor* visitor) const {
+    visitor->Trace(thumb_color_);
+    visitor->Trace(track_color_);
+  }
+
   StyleColor GetThumbColor() const { return thumb_color_; }
   StyleColor GetTrackColor() const { return track_color_; }
 
diff --git a/third_party/blink/renderer/core/style/svg_paint.h b/third_party/blink/renderer/core/style/svg_paint.h
index b1afecc..ed032db5 100644
--- a/third_party/blink/renderer/core/style/svg_paint.h
+++ b/third_party/blink/renderer/core/style/svg_paint.h
@@ -58,7 +58,10 @@
   CORE_EXPORT ~SVGPaint();
   CORE_EXPORT SVGPaint& operator=(const SVGPaint& paint);
 
-  void Trace(Visitor* visitor) const { visitor->Trace(resource); }
+  void Trace(Visitor* visitor) const {
+    visitor->Trace(color);
+    visitor->Trace(resource);
+  }
 
   CORE_EXPORT bool operator==(const SVGPaint&) const;
   bool operator!=(const SVGPaint& other) const { return !(*this == other); }
diff --git a/third_party/blink/renderer/core/svg/build.gni b/third_party/blink/renderer/core/svg/build.gni
index aaebed4..4069782 100644
--- a/third_party/blink/renderer/core/svg/build.gni
+++ b/third_party/blink/renderer/core/svg/build.gni
@@ -286,7 +286,6 @@
   "svg_resource_client.h",
   "svg_resource_document_content.cc",
   "svg_resource_document_content.h",
-  "svg_resource_document_observer.h",
   "svg_script_element.cc",
   "svg_script_element.h",
   "svg_set_element.cc",
diff --git a/third_party/blink/renderer/core/svg/svg_animated_color.h b/third_party/blink/renderer/core/svg/svg_animated_color.h
index 7604f1b..0dff2f77 100644
--- a/third_party/blink/renderer/core/svg/svg_animated_color.h
+++ b/third_party/blink/renderer/core/svg/svg_animated_color.h
@@ -44,6 +44,11 @@
  public:
   explicit SVGColorProperty(const String&);
 
+  void Trace(Visitor* visitor) const override {
+    visitor->Trace(style_color_);
+    SVGPropertyBase::Trace(visitor);
+  }
+
   SVGPropertyBase* CloneForAnimation(const String&) const override;
   String ValueAsString() const override;
 
diff --git a/third_party/blink/renderer/core/svg/svg_resource.cc b/third_party/blink/renderer/core/svg/svg_resource.cc
index 54f73fc2..0b0eade 100644
--- a/third_party/blink/renderer/core/svg/svg_resource.cc
+++ b/third_party/blink/renderer/core/svg/svg_resource.cc
@@ -241,11 +241,7 @@
   ResourceLoaderOptions options(execution_context->GetCurrentWorld());
   options.initiator_info.name = fetch_initiator_type_names::kCSS;
   FetchParameters params(ResourceRequest(url_), options);
-  document_content_ = SVGResourceDocumentContent::Fetch(params, document);
-  if (!document_content_) {
-    return;
-  }
-  document_content_->AddObserver(this);
+  document_content_ = SVGResourceDocumentContent::Fetch(params, document, this);
   target_ = ResolveTarget();
 }
 
@@ -262,17 +258,11 @@
   FetchParameters params(ResourceRequest(url_), options);
   params.SetContentSecurityCheck(
       network::mojom::blink::CSPDisposition::DO_NOT_CHECK);
-  document_content_ = SVGResourceDocumentContent::Fetch(params, document);
-  if (!document_content_) {
-    return;
-  }
-  document_content_->AddObserver(this);
+  document_content_ = SVGResourceDocumentContent::Fetch(params, document, this);
   target_ = ResolveTarget();
 }
 
-void ExternalSVGResource::ResourceNotifyFinished(
-    SVGResourceDocumentContent* document_content) {
-  DCHECK_EQ(document_content_, document_content);
+void ExternalSVGResource::NotifyFinished(Resource*) {
   Element* new_target = ResolveTarget();
   if (new_target == target_)
     return;
@@ -280,6 +270,10 @@
   NotifyContentChanged();
 }
 
+String ExternalSVGResource::DebugName() const {
+  return "ExternalSVGResource";
+}
+
 Element* ExternalSVGResource::ResolveTarget() {
   if (!document_content_)
     return nullptr;
@@ -296,6 +290,7 @@
 void ExternalSVGResource::Trace(Visitor* visitor) const {
   visitor->Trace(document_content_);
   SVGResource::Trace(visitor);
+  ResourceClient::Trace(visitor);
 }
 
 ExternalSVGResourceImageContent::ExternalSVGResourceImageContent(
diff --git a/third_party/blink/renderer/core/svg/svg_resource.h b/third_party/blink/renderer/core/svg/svg_resource.h
index d183722..5555876 100644
--- a/third_party/blink/renderer/core/svg/svg_resource.h
+++ b/third_party/blink/renderer/core/svg/svg_resource.h
@@ -6,10 +6,9 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_SVG_SVG_RESOURCE_H_
 
 #include "third_party/blink/renderer/core/loader/resource/image_resource_observer.h"
-#include "third_party/blink/renderer/core/svg/svg_resource_document_observer.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/heap/prefinalizer.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_client.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
@@ -149,8 +148,7 @@
 };
 
 // External resource reference (see SVGResource).
-class ExternalSVGResource final : public SVGResource,
-                                  public SVGResourceDocumentObserver {
+class ExternalSVGResource final : public SVGResource, public ResourceClient {
  public:
   explicit ExternalSVGResource(const KURL&);
 
@@ -162,8 +160,9 @@
  private:
   Element* ResolveTarget();
 
-  // SVGResourceDocumentObserver:
-  void ResourceNotifyFinished(SVGResourceDocumentContent*) override;
+  // ResourceClient implementation
+  void NotifyFinished(Resource*) override;
+  WTF::String DebugName() const override;
 
   Member<SVGResourceDocumentContent> document_content_;
   KURL url_;
diff --git a/third_party/blink/renderer/core/svg/svg_resource_document_content.cc b/third_party/blink/renderer/core/svg/svg_resource_document_content.cc
index 03c74ae..83c307d3 100644
--- a/third_party/blink/renderer/core/svg/svg_resource_document_content.cc
+++ b/third_party/blink/renderer/core/svg/svg_resource_document_content.cc
@@ -22,16 +22,15 @@
 
 #include "third_party/blink/renderer/core/svg/svg_resource_document_content.h"
 
-#include "base/task/single_thread_task_runner.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/document_init.h"
 #include "third_party/blink/renderer/core/dom/xml_document.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
-#include "third_party/blink/renderer/core/loader/resource/svg_document_resource.h"
-#include "third_party/blink/renderer/core/svg/svg_resource_document_observer.h"
+#include "third_party/blink/renderer/core/loader/resource/text_resource.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
-#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_client.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
 
 namespace blink {
@@ -46,14 +45,13 @@
   static SVGExternalDocumentCache* From(Document&);
   explicit SVGExternalDocumentCache(Document&);
 
-  SVGResourceDocumentContent* Get(const String& url_without_fragment);
-  void Put(const String& url_without_fragment,
-           SVGResourceDocumentContent* content);
+  SVGResourceDocumentContent* Get(TextResource*);
 
   void Trace(Visitor*) const override;
 
  private:
-  HeapHashMap<String, WeakMember<SVGResourceDocumentContent>> entries_;
+  HeapHashMap<WeakMember<Resource>, Member<SVGResourceDocumentContent>>
+      entries_;
 };
 
 const char SVGExternalDocumentCache::kSupplementName[] =
@@ -73,14 +71,13 @@
     : Supplement<Document>(document) {}
 
 SVGResourceDocumentContent* SVGExternalDocumentCache::Get(
-    const String& url_without_fragment) {
-  auto it = entries_.find(url_without_fragment);
-  return it != entries_.end() ? it->value : nullptr;
-}
-
-void SVGExternalDocumentCache::Put(const String& url_without_fragment,
-                                   SVGResourceDocumentContent* content) {
-  entries_.Set(url_without_fragment, content);
+    TextResource* resource) {
+  auto& entry = entries_.insert(resource, nullptr).stored_value->value;
+  if (!entry) {
+    entry = MakeGarbageCollected<SVGResourceDocumentContent>(
+        resource, GetSupplementable()->GetExecutionContext());
+  }
+  return entry.Get();
 }
 
 void SVGExternalDocumentCache::Trace(Visitor* visitor) const {
@@ -88,146 +85,63 @@
   visitor->Trace(entries_);
 }
 
-bool CanReuseContent(const SVGResourceDocumentContent& content) {
-  // Don't reuse if loading failed.
-  return !content.ErrorOccurred();
+bool MimeTypeAllowed(const ResourceResponse& response) {
+  AtomicString mime_type = response.MimeType();
+  if (response.IsHTTP())
+    mime_type = response.HttpContentType();
+  return mime_type == "image/svg+xml" || mime_type == "text/xml" ||
+         mime_type == "application/xml" || mime_type == "application/xhtml+xml";
+}
+
+Document* CreateDocument(const TextResource* resource,
+                         ExecutionContext* execution_context) {
+  const ResourceResponse& response = resource->GetResponse();
+  if (!MimeTypeAllowed(response))
+    return nullptr;
+  auto* document =
+      XMLDocument::CreateSVG(DocumentInit::Create()
+                                 .WithURL(response.CurrentRequestUrl())
+                                 .WithExecutionContext(execution_context)
+                                 .WithAgent(*execution_context->GetAgent()));
+  document->SetContent(resource->DecodedText());
+  return document;
 }
 
 }  // namespace
 
-SVGResourceDocumentContent::SVGResourceDocumentContent(
-    ExecutionContext* context,
-    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
-    : context_(context), task_runner_(std::move(task_runner)) {
-  DCHECK(context_);
-}
-
-SVGResourceDocumentContent::~SVGResourceDocumentContent() = default;
-
-void SVGResourceDocumentContent::NotifyStartLoad() {
-  // Check previous status.
-  switch (status_) {
-    case ResourceStatus::kPending:
-      CHECK(false);
-      break;
-
-    case ResourceStatus::kNotStarted:
-      // Normal load start.
-      break;
-
-    case ResourceStatus::kCached:
-    case ResourceStatus::kLoadError:
-    case ResourceStatus::kDecodeError:
-      // Load start due to revalidation/reload.
-      break;
+Document* SVGResourceDocumentContent::GetDocument() {
+  if (resource_->IsLoaded()) {
+    // If this entry saw a revalidation, re-parse the document.
+    // TODO(fs): This will be inefficient for successful revalidations, so we
+    // want to detect those and not re-parse the document in those cases.
+    if (was_revalidating_) {
+      document_.Clear();
+      was_revalidating_ = false;
+    }
+    if (!document_ && resource_->HasData())
+      document_ = CreateDocument(resource_, context_);
   }
-  status_ = ResourceStatus::kPending;
-}
-
-void SVGResourceDocumentContent::UpdateStatus(ResourceStatus new_status) {
-  switch (new_status) {
-    case ResourceStatus::kCached:
-    case ResourceStatus::kPending:
-      // In case of successful load, Resource's status can be kCached or
-      // kPending. Set it to kCached in both cases.
-      new_status = ResourceStatus::kCached;
-      break;
-
-    case ResourceStatus::kLoadError:
-    case ResourceStatus::kDecodeError:
-      // In case of error, Resource's status is set to an error status before
-      // updating the document and thus we use the error status as-is.
-      break;
-
-    case ResourceStatus::kNotStarted:
-      CHECK(false);
-      break;
-  }
-  status_ = new_status;
-}
-
-void SVGResourceDocumentContent::UpdateDocument(const String& content,
-                                                const KURL& request_url) {
-  if (content.empty()) {
-    return;
-  }
-  url_ = request_url;
-  document_ = XMLDocument::CreateSVG(DocumentInit::Create()
-                                         .WithURL(request_url)
-                                         .WithExecutionContext(context_)
-                                         .WithAgent(*context_->GetAgent()));
-  document_->SetContent(content);
-}
-
-void SVGResourceDocumentContent::ClearDocument() {
-  document_.Clear();
-}
-
-Document* SVGResourceDocumentContent::GetDocument() const {
   return document_.Get();
 }
 
 const KURL& SVGResourceDocumentContent::Url() const {
-  return url_;
-}
-
-void SVGResourceDocumentContent::AddObserver(
-    SVGResourceDocumentObserver* observer) {
-  // We currently don't have any N:1 relations (multiple observer registrations
-  // for a single document content) among the existing clients
-  // (ExternalSVGResource and SVGUseElement).
-  DCHECK(!observers_.Contains(observer));
-  observers_.insert(observer);
-  if (IsLoaded()) {
-    task_runner_->PostTask(
-        FROM_HERE,
-        WTF::BindOnce(&SVGResourceDocumentContent::NotifyObserver,
-                      WrapPersistent(this), WrapWeakPersistent(observer)));
-  }
-}
-
-void SVGResourceDocumentContent::RemoveObserver(
-    SVGResourceDocumentObserver* observer) {
-  observers_.erase(observer);
-}
-
-void SVGResourceDocumentContent::NotifyObserver(
-    SVGResourceDocumentObserver* observer) {
-  if (observer && observers_.Contains(observer)) {
-    observer->ResourceNotifyFinished(this);
-  }
-}
-
-void SVGResourceDocumentContent::NotifyObservers() {
-  for (auto& observer : observers_) {
-    observer->ResourceNotifyFinished(this);
-  }
-}
-
-bool SVGResourceDocumentContent::IsLoaded() const {
-  return status_ > ResourceStatus::kPending;
+  return resource_->Url();
 }
 
 bool SVGResourceDocumentContent::IsLoading() const {
-  return status_ == ResourceStatus::kPending;
-}
-
-bool SVGResourceDocumentContent::ErrorOccurred() const {
-  return status_ == ResourceStatus::kLoadError ||
-         status_ == ResourceStatus::kDecodeError;
+  return resource_->IsLoading();
 }
 
 void SVGResourceDocumentContent::Trace(Visitor* visitor) const {
+  visitor->Trace(resource_);
   visitor->Trace(document_);
   visitor->Trace(context_);
-  visitor->Trace(observers_);
 }
 
 SVGResourceDocumentContent* SVGResourceDocumentContent::Fetch(
     FetchParameters& params,
-    Document& document) {
-  CHECK(!params.Url().IsNull());
-
+    Document& document,
+    ResourceClient* client) {
   params.MutableResourceRequest().SetMode(
       network::mojom::blink::RequestMode::kSameOrigin);
   DCHECK_EQ(params.GetResourceRequest().GetRequestContext(),
@@ -235,22 +149,15 @@
   params.SetRequestContext(mojom::blink::RequestContextType::IMAGE);
   params.SetRequestDestination(network::mojom::RequestDestination::kImage);
 
-  const KURL url_without_fragment =
-      MemoryCache::RemoveFragmentIdentifierIfNeeded(params.Url());
-  auto* cache = SVGExternalDocumentCache::From(document);
-
-  auto* cached_content = cache->Get(url_without_fragment.GetString());
-  if (cached_content && CanReuseContent(*cached_content)) {
-    return cached_content;
-  }
-
-  SVGDocumentResource* resource = SVGDocumentResource::Fetch(
-      params, document.Fetcher(), document.GetExecutionContext());
-  if (!resource) {
+  TextResource* resource =
+      TextResource::FetchSVGDocument(params, document.Fetcher(), client);
+  if (!resource)
     return nullptr;
-  }
-  cache->Put(url_without_fragment, resource->GetContent());
-  return resource->GetContent();
+  auto* document_content =
+      SVGExternalDocumentCache::From(document)->Get(resource);
+  if (resource->IsCacheValidator())
+    document_content->SetWasRevalidating();
+  return document_content;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/svg/svg_resource_document_content.h b/third_party/blink/renderer/core/svg/svg_resource_document_content.h
index 550323f..5dbeea40 100644
--- a/third_party/blink/renderer/core/svg/svg_resource_document_content.h
+++ b/third_party/blink/renderer/core/svg/svg_resource_document_content.h
@@ -24,19 +24,8 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_SVG_SVG_RESOURCE_DOCUMENT_CONTENT_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
-#include "third_party/blink/renderer/platform/loader/fetch/resource_status.h"
-#include "third_party/blink/renderer/platform/weborigin/kurl.h"
-
-namespace base {
-class SingleThreadTaskRunner;
-}  // namespace base
-
-namespace WTF {
-class String;
-}  // namespace WTF
 
 namespace blink {
 
@@ -44,55 +33,36 @@
 class ExecutionContext;
 class FetchParameters;
 class KURL;
-class SVGResourceDocumentObserver;
+class ResourceClient;
+class TextResource;
 
-// Representation of an SVG resource document. Fed from an SVGDocumentResource
-// that update loading status and provide the document text content. Thus the
-// "complex" made up of these two classes manage the load cycle for the content
-// document. The load cycle of the complete content document can differ from
-// that of the underlying resource if the content document itself has (data
-// URL) subresources.
 class CORE_EXPORT SVGResourceDocumentContent final
     : public GarbageCollected<SVGResourceDocumentContent> {
  public:
-  static SVGResourceDocumentContent* Fetch(FetchParameters&, Document&);
+  static SVGResourceDocumentContent* Fetch(FetchParameters&,
+                                           Document&,
+                                           ResourceClient*);
 
-  SVGResourceDocumentContent(ExecutionContext*,
-                             scoped_refptr<base::SingleThreadTaskRunner>);
-  ~SVGResourceDocumentContent();
+  SVGResourceDocumentContent(TextResource* resource, ExecutionContext* context)
+      : resource_(resource), context_(context) {
+    DCHECK(resource_);
+    DCHECK(context_);
+  }
 
-  bool IsLoaded() const;
-  bool IsLoading() const;
-  bool ErrorOccurred() const;
-
-  Document* GetDocument() const;
-
-  void NotifyStartLoad();
-
-  // Update the contained document using the text data in `content`, using
-  // `request_url` as the document URL.
-  void UpdateDocument(const WTF::String& content, const KURL& request_url);
-  void ClearDocument();
-
-  void UpdateStatus(ResourceStatus new_status);
-
+  Document* GetDocument();
   const KURL& Url() const;
 
-  void AddObserver(SVGResourceDocumentObserver*);
-  void RemoveObserver(SVGResourceDocumentObserver*);
-  void NotifyObservers();
+  bool IsLoading() const;
 
   void Trace(Visitor*) const;
 
  private:
-  void NotifyObserver(SVGResourceDocumentObserver*);
+  void SetWasRevalidating() { was_revalidating_ = true; }
 
+  Member<TextResource> resource_;
   Member<Document> document_;
   Member<ExecutionContext> context_;
-  HeapHashSet<WeakMember<SVGResourceDocumentObserver>> observers_;
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-  KURL url_;
-  ResourceStatus status_ = ResourceStatus::kNotStarted;
+  bool was_revalidating_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/svg/svg_resource_document_content_test.cc b/third_party/blink/renderer/core/svg/svg_resource_document_content_test.cc
index e2933b5..97bdcb51 100644
--- a/third_party/blink/renderer/core/svg/svg_resource_document_content_test.cc
+++ b/third_party/blink/renderer/core/svg/svg_resource_document_content_test.cc
@@ -28,7 +28,8 @@
   ResourceLoaderOptions options(execution_context->GetCurrentWorld());
   options.initiator_info.name = fetch_initiator_type_names::kCSS;
   FetchParameters params(ResourceRequest(kSVGUrl), options);
-  auto* entry = SVGResourceDocumentContent::Fetch(params, GetDocument());
+  auto* entry =
+      SVGResourceDocumentContent::Fetch(params, GetDocument(), nullptr);
 
   // Write part of the response. The document should not be initialized yet,
   // because the response is not complete. The document would be invalid at this
diff --git a/third_party/blink/renderer/core/svg/svg_resource_document_observer.h b/third_party/blink/renderer/core/svg/svg_resource_document_observer.h
deleted file mode 100644
index ff15514..0000000
--- a/third_party/blink/renderer/core/svg/svg_resource_document_observer.h
+++ /dev/null
@@ -1,26 +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 THIRD_PARTY_BLINK_RENDERER_CORE_SVG_SVG_RESOURCE_DOCUMENT_OBSERVER_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_SVG_SVG_RESOURCE_DOCUMENT_OBSERVER_H_
-
-#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-
-namespace blink {
-
-class SVGResourceDocumentContent;
-
-// Observer for changes in a SVGResourceDocumentContent.
-class SVGResourceDocumentObserver : public GarbageCollectedMixin {
- public:
-  virtual ~SVGResourceDocumentObserver() = default;
-
-  // The resource document and any resources referenced by it has finished
-  // loading ('load' has fired).
-  virtual void ResourceNotifyFinished(SVGResourceDocumentContent*) = 0;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SVG_SVG_RESOURCE_DOCUMENT_OBSERVER_H_
diff --git a/third_party/blink/renderer/core/svg/svg_use_element.cc b/third_party/blink/renderer/core/svg/svg_use_element.cc
index 976a3f1..b97ba58 100644
--- a/third_party/blink/renderer/core/svg/svg_use_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_use_element.cc
@@ -99,6 +99,7 @@
   visitor->Trace(target_id_observer_);
   SVGGraphicsElement::Trace(visitor);
   SVGURIReference::Trace(visitor);
+  ResourceClient::Trace(visitor);
 }
 
 #if DCHECK_IS_ON()
@@ -191,27 +192,14 @@
   return !document_content_ || !document_content_->IsLoading();
 }
 
-void SVGUseElement::UpdateDocumentContent(
-    SVGResourceDocumentContent* document_content) {
-  if (document_content_ == document_content) {
-    return;
-  }
-  if (document_content_) {
-    document_content_->RemoveObserver(this);
-  }
-  document_content_ = document_content;
-  if (document_content_) {
-    document_content_->AddObserver(this);
-  }
-}
-
 void SVGUseElement::UpdateTargetReference() {
   const String& url_string = HrefString();
   element_url_ = GetDocument().CompleteURL(url_string);
   element_url_is_local_ = url_string.StartsWith('#');
   if (!IsStructurallyExternal() || !GetDocument().IsActive()) {
-    UpdateDocumentContent(nullptr);
+    ClearResource();
     pending_event_.Cancel();
+    document_content_ = nullptr;
     return;
   }
   if (!element_url_.HasFragmentIdentifier() ||
@@ -232,9 +220,8 @@
   ResourceLoaderOptions options(execution_context->GetCurrentWorld());
   options.initiator_info.name = fetch_initiator_type_names::kUse;
   FetchParameters params(ResourceRequest(element_url_), options);
-  auto* document_content =
-      SVGResourceDocumentContent::Fetch(params, *context_document);
-  UpdateDocumentContent(document_content);
+  document_content_ =
+      SVGResourceDocumentContent::Fetch(params, *context_document, this);
 }
 
 void SVGUseElement::SvgAttributeChanged(
@@ -629,14 +616,13 @@
   }
 }
 
-void SVGUseElement::ResourceNotifyFinished(
-    SVGResourceDocumentContent* document_content) {
-  DCHECK_EQ(document_content_, document_content);
+void SVGUseElement::NotifyFinished(Resource* resource) {
   if (!isConnected())
     return;
   InvalidateShadowTree();
 
-  const bool is_error = document_content->ErrorOccurred();
+  const bool is_error =
+      resource->ErrorOccurred() || !document_content_->GetDocument();
   const AtomicString& event_name =
       is_error ? event_type_names::kError : event_type_names::kLoad;
   DCHECK(!pending_event_.IsActive());
@@ -646,6 +632,10 @@
                     WrapPersistent(this), event_name));
 }
 
+String SVGUseElement::DebugName() const {
+  return "SVGUseElement";
+}
+
 SVGAnimatedPropertyBase* SVGUseElement::PropertyFromAttribute(
     const QualifiedName& attribute_name) const {
   if (attribute_name == svg_names::kXAttr) {
diff --git a/third_party/blink/renderer/core/svg/svg_use_element.h b/third_party/blink/renderer/core/svg/svg_use_element.h
index 61d98a00..88aba293 100644
--- a/third_party/blink/renderer/core/svg/svg_use_element.h
+++ b/third_party/blink/renderer/core/svg/svg_use_element.h
@@ -26,9 +26,9 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/svg/svg_geometry_element.h"
 #include "third_party/blink/renderer/core/svg/svg_graphics_element.h"
-#include "third_party/blink/renderer/core/svg/svg_resource_document_observer.h"
 #include "third_party/blink/renderer/core/svg/svg_uri_reference.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_client.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
 
 namespace blink {
@@ -38,7 +38,7 @@
 
 class SVGUseElement final : public SVGGraphicsElement,
                             public SVGURIReference,
-                            public SVGResourceDocumentObserver {
+                            public ResourceClient {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -104,11 +104,8 @@
                               const SVGElement& new_target) const;
 
   void QueueOrDispatchPendingEvent(const AtomicString&);
-
-  // SVGResourceDocumentObserver:
-  void ResourceNotifyFinished(SVGResourceDocumentContent*) override;
-
-  void UpdateDocumentContent(SVGResourceDocumentContent*);
+  void NotifyFinished(Resource*) override;
+  String DebugName() const override;
   void UpdateTargetReference();
 
   SVGAnimatedPropertyBase* PropertyFromAttribute(
diff --git a/third_party/blink/renderer/modules/accessibility/blink_ax_tree_source.cc b/third_party/blink/renderer/modules/accessibility/blink_ax_tree_source.cc
index 721fdec3..a0cdb4f1 100644
--- a/third_party/blink/renderer/modules/accessibility/blink_ax_tree_source.cc
+++ b/third_party/blink/renderer/modules/accessibility/blink_ax_tree_source.cc
@@ -270,16 +270,18 @@
   // The child may be invalid due to issues in blink accessibility code.
   CHECK(child);
   if (child->IsDetached()) {
-    NOTREACHED() << "Should not try to serialize an invalid child:"
-                 << "\nParent: " << node->ToString(true).Utf8()
-                 << "\nChild: " << child->ToString(true).Utf8();
+    NOTREACHED(base::NotFatalUntil::M127)
+        << "Should not try to serialize an invalid child:" << "\nParent: "
+        << node->ToString(true).Utf8()
+        << "\nChild: " << child->ToString(true).Utf8();
     return nullptr;
   }
 
   if (!child->AccessibilityIsIncludedInTree()) {
-    NOTREACHED() << "Should not receive unincluded child."
-                 << "\nChild: " << child->ToString(true).Utf8()
-                 << "\nParent: " << node->ToString(true).Utf8();
+    NOTREACHED(base::NotFatalUntil::M127)
+        << "Should not receive unincluded child."
+        << "\nChild: " << child->ToString(true).Utf8()
+        << "\nParent: " << node->ToString(true).Utf8();
     return nullptr;
   }
 
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
index 797e343..37b44d2 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
@@ -751,17 +751,11 @@
       WGPUTextureUsage_CopyDst | WGPUTextureUsage_RenderAttachment,
       dst_mailbox);
   WGPUImageCopyTexture source = {
-      .nextInChain = nullptr,
       .texture = texture,
-      .mipLevel = 0,
-      .origin = WGPUOrigin3D{0},
       .aspect = WGPUTextureAspect_All,
   };
   WGPUImageCopyTexture destination = {
-      .nextInChain = nullptr,
       .texture = reservation.texture,
-      .mipLevel = 0,
-      .origin = WGPUOrigin3D{0},
       .aspect = WGPUTextureAspect_All,
   };
   WGPUExtent3D copy_size = {
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc b/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc
index 17a4e368..50c90ef5 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc
@@ -182,7 +182,6 @@
   DCHECK(webgpu_view->buffer());
 
   WGPUImageCopyBuffer dawn_view = {};
-  dawn_view.nextInChain = nullptr;
   dawn_view.buffer = webgpu_view->buffer()->GetHandle();
 
   *error = ValidateTextureDataLayout(webgpu_view, &dawn_view.layout);
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
index 54997e5..6e7e5d92 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
@@ -291,7 +291,7 @@
     // created multiple subsequences. If DCHECK_IS_ON(), then we should have
     // encountered the DCHECK at the end of EndSubsequence() during the previous
     // paint.
-    NOTREACHED();
+    DUMP_WILL_BE_NOTREACHED_NORETURN();
     return false;
   }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 1b0e13ef..d0c5f1bd 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -1833,15 +1833,6 @@
     return {RevalidationPolicy::kReload, "Reload due to type mismatch."};
   }
 
-  // Always create a new resource for SVG resource documents since they are
-  // tied to the requesting document. There's a document-scoped cache in-front
-  // of the ResourceFetcher that will handle reuse (see
-  // SVGResourceDocumentContent::Fetch()).
-  if (type == ResourceType::kSVGDocument) {
-    return {RevalidationPolicy::kReload,
-            "SVG resource documents cannot be reused."};
-  }
-
   // If resource was populated from archive or data: url, use it.
   // This doesn't necessarily mean that |resource| was just created by using
   // CreateResourceForStaticData().
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index bb570f1..e2e0c93 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -959,7 +959,7 @@
     },
     {
       // Support the position-visibility property for anchor positioning.
-      // https://github.com/w3c/csswg-drafts/issues/7758
+      // https://drafts.csswg.org/css-anchor-position-1/#position-visibility
       // TODO(crbug.com/332933527): Support anchors-valid and no-overflow. For
       // now, this only supports always and anchors-visible.
       name: "CSSPositionVisibility",
@@ -2023,6 +2023,13 @@
       status: "stable",
       base_feature: "none",
     },
+    // The `anchor` attribute, supported by both anchor positioning and the
+    // popover API.
+    {
+      name: "HTMLAnchorAttribute",
+      status: "experimental",
+      implied_by: ["CSSAnchorPositioning"]
+    },
     // Adds support for the experimental `interesttarget`
     // attributes, as specified in the open-ui "Interest Invokers" explainer.
     // https://open-ui.org/components/interest-invokers.explainer/
diff --git a/third_party/blink/tools/blinkpy/w3c/buganizer.py b/third_party/blink/tools/blinkpy/w3c/buganizer.py
index d900547..576d007 100644
--- a/third_party/blink/tools/blinkpy/w3c/buganizer.py
+++ b/third_party/blink/tools/blinkpy/w3c/buganizer.py
@@ -215,8 +215,8 @@
     def NewComment(self, issue_id: IssueID, comment: str):
         """Makes a request to the issue tracker to add a comment."""
         new_comment_request = {'issueComment': {'comment': comment}}
-        request = self._service.issues().modify(
-            issueId=self._ResolveID(issue_id), body=new_comment_request)
+        request = self._service.issues().modify(issueId=str(
+            self._ResolveID(issue_id)), body=new_comment_request)
         try:
             return self._ExecuteRequest(request)
         except Exception as e:
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py b/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py
index c2ab0d09..beaa2cd3 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py
@@ -757,8 +757,20 @@
             return
         if not isinstance(data, str):
             data = json.dumps(data, sort_keys=True)
-        browser_log = self.browser_logs.setdefault(int(process), io.StringIO())
-        browser_log.write(f'{data}\n')
+        # TODO(crbug.com/333782826): Remove after addressing non-integer values
+        # by wptrunner's Android drivers:
+        # https://github.com/web-platform-tests/wpt/blob/073f56c2/tools/wptrunner/wptrunner/browsers/chrome_android.py#L126
+        #
+        # which does not adhere to:
+        # https://firefox-source-docs.mozilla.org/mozbase/mozlog.html
+        #
+        # which says that `process` is a PID.
+        try:
+            browser_log = self.browser_logs.setdefault(int(process),
+                                                       io.StringIO())
+            browser_log.write(f'{data}\n')
+        except ValueError:
+            pass
 
     def _write_text_results(self, result: WPTResult, artifacts: Artifacts):
         """Write actual, expected, and diff text outputs to disk, if possible.
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/driver.py b/third_party/blink/tools/blinkpy/web_tests/port/driver.py
index 6d39747..c505d3c 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/driver.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/driver.py
@@ -168,7 +168,109 @@
     pass
 
 
-class Driver(object):
+class TestURIMapper:
+
+    def __init__(self, port):
+        self.WPT_DIRS = port.WPT_DIRS
+        self._port = port
+
+    # The *_HOST_AND_PORTS tuples are (hostname, insecure_port, secure_port),
+    # i.e. the information needed to create HTTP and HTTPS URLs.
+    # TODO(burnik): Read from config or args.
+    HTTP_DIR = 'http/tests/'
+    HTTP_LOCAL_DIR = 'http/tests/local/'
+    HTTP_HOST_AND_PORTS = ('127.0.0.1', 8000, 8443)
+    WPT_HOST_AND_PORTS = ('web-platform.test', 8001, 8444)
+    WPT_H2_PORT = 9000
+
+    def is_http_test(self, test_name):
+        return (test_name.startswith(self.HTTP_DIR)
+                and not test_name.startswith(self.HTTP_LOCAL_DIR))
+
+    def test_to_uri(self, test_name):
+        """Convert a test name to a URI.
+
+        Tests which have an 'https' directory in their paths or '.https.' or
+        '.serviceworker.' in their name will be loaded over HTTPS; all other
+        tests over HTTP. Example paths loaded over HTTPS:
+        http/tests/security/mixedContent/https/test1.html
+        http/tests/security/mixedContent/test1.https.html
+        external/wpt/encoding/idlharness.any.serviceworker.html
+        """
+        using_wptserve = self._port.should_use_wptserve(test_name)
+
+        if not self.is_http_test(test_name) and not using_wptserve:
+            return path.abspath_to_uri(self._port.host.platform,
+                                       self._port.abspath_for_test(test_name))
+
+        if using_wptserve:
+            for wpt_path, url_prefix in self.WPT_DIRS.items():
+                # The keys of WPT_DIRS do not have trailing slashes.
+                wpt_path += '/'
+                if test_name.startswith(wpt_path):
+                    test_dir_prefix = wpt_path
+                    test_url_prefix = url_prefix
+                    break
+            else:
+                # We really shouldn't reach here, but in case we do, fail gracefully.
+                _log.error('Unrecognized WPT test name: %s', test_name)
+                test_dir_prefix = 'external/wpt/'
+                test_url_prefix = '/'
+            hostname, insecure_port, secure_port = self.WPT_HOST_AND_PORTS
+            if '.www.' in test_name:
+                hostname = "www.%s" % hostname
+            if '.h2.' in test_name:
+                secure_port = self.WPT_H2_PORT
+        else:
+            test_dir_prefix = self.HTTP_DIR
+            test_url_prefix = '/'
+            hostname, insecure_port, secure_port = self.HTTP_HOST_AND_PORTS
+
+        relative_path = test_name[len(test_dir_prefix):]
+
+        if ('/https/' in test_name or '.https.' in test_name
+                or '.h2.' in test_name or '.serviceworker.' in test_name
+                or '.serviceworker-module.' in test_name):
+            return 'https://%s:%d%s%s' % (hostname, secure_port,
+                                          test_url_prefix, relative_path)
+        return 'http://%s:%d%s%s' % (hostname, insecure_port, test_url_prefix,
+                                     relative_path)
+
+    def _get_uri_prefixes(self, hostname, insecure_port, secure_port):
+        """Returns the HTTP and HTTPS URI prefix for a hostname."""
+        return [
+            'http://%s:%d/' % (hostname, insecure_port),
+            'https://%s:%d/' % (hostname, secure_port)
+        ]
+
+    def uri_to_test(self, uri):
+        """Return the base web test name for a given URI.
+
+        This returns the test name for a given URI, e.g., if you passed in
+        "file:///src/web_tests/fast/html/keygen.html" it would return
+        "fast/html/keygen.html".
+        """
+
+        if uri.startswith('file:///'):
+            prefix = path.abspath_to_uri(self._port.host.platform,
+                                         self._port.web_tests_dir())
+            if not prefix.endswith('/'):
+                prefix += '/'
+            return uri[len(prefix):]
+
+        for prefix in self._get_uri_prefixes(*self.HTTP_HOST_AND_PORTS):
+            if uri.startswith(prefix):
+                return self.HTTP_DIR + uri[len(prefix):]
+        for prefix in self._get_uri_prefixes(*self.WPT_HOST_AND_PORTS):
+            if uri.startswith(prefix):
+                url_path = '/' + uri[len(prefix):]
+                for wpt_path, url_prefix in self.WPT_DIRS.items():
+                    if url_path.startswith(url_prefix):
+                        return wpt_path + '/' + url_path[len(url_prefix):]
+        raise NotImplementedError('unknown url type: %s' % uri)
+
+
+class Driver(TestURIMapper):
     """object for running test(s) using content_shell or other driver."""
 
     def __init__(self, port, worker_number, no_timeout=False):
@@ -180,8 +282,7 @@
         port - reference back to the port object.
         worker_number - identifier for a particular worker/driver instance
         """
-        self.WPT_DIRS = port.WPT_DIRS
-        self._port = port
+        super().__init__(port)
         self._worker_number = worker_number
         self._no_timeout = no_timeout
 
@@ -353,101 +454,6 @@
                                          self._crashed_pid, stdout, stderr,
                                          newer_than)
 
-    # The *_HOST_AND_PORTS tuples are (hostname, insecure_port, secure_port),
-    # i.e. the information needed to create HTTP and HTTPS URLs.
-    # TODO(burnik): Read from config or args.
-    HTTP_DIR = 'http/tests/'
-    HTTP_LOCAL_DIR = 'http/tests/local/'
-    HTTP_HOST_AND_PORTS = ('127.0.0.1', 8000, 8443)
-    WPT_HOST_AND_PORTS = ('web-platform.test', 8001, 8444)
-    WPT_H2_PORT = 9000
-
-    def is_http_test(self, test_name):
-        return (test_name.startswith(self.HTTP_DIR)
-                and not test_name.startswith(self.HTTP_LOCAL_DIR))
-
-    def test_to_uri(self, test_name):
-        """Convert a test name to a URI.
-
-        Tests which have an 'https' directory in their paths or '.https.' or
-        '.serviceworker.' in their name will be loaded over HTTPS; all other
-        tests over HTTP. Example paths loaded over HTTPS:
-        http/tests/security/mixedContent/https/test1.html
-        http/tests/security/mixedContent/test1.https.html
-        external/wpt/encoding/idlharness.any.serviceworker.html
-        """
-        using_wptserve = self._port.should_use_wptserve(test_name)
-
-        if not self.is_http_test(test_name) and not using_wptserve:
-            return path.abspath_to_uri(self._port.host.platform,
-                                       self._port.abspath_for_test(test_name))
-
-        if using_wptserve:
-            for wpt_path, url_prefix in self.WPT_DIRS.items():
-                # The keys of WPT_DIRS do not have trailing slashes.
-                wpt_path += '/'
-                if test_name.startswith(wpt_path):
-                    test_dir_prefix = wpt_path
-                    test_url_prefix = url_prefix
-                    break
-            else:
-                # We really shouldn't reach here, but in case we do, fail gracefully.
-                _log.error('Unrecognized WPT test name: %s', test_name)
-                test_dir_prefix = 'external/wpt/'
-                test_url_prefix = '/'
-            hostname, insecure_port, secure_port = self.WPT_HOST_AND_PORTS
-            if '.www.' in test_name:
-                hostname = "www.%s" % hostname
-            if '.h2.' in test_name:
-                secure_port = self.WPT_H2_PORT
-        else:
-            test_dir_prefix = self.HTTP_DIR
-            test_url_prefix = '/'
-            hostname, insecure_port, secure_port = self.HTTP_HOST_AND_PORTS
-
-        relative_path = test_name[len(test_dir_prefix):]
-
-        if ('/https/' in test_name or '.https.' in test_name
-                or '.h2.' in test_name or '.serviceworker.' in test_name
-                or '.serviceworker-module.' in test_name):
-            return 'https://%s:%d%s%s' % (hostname, secure_port,
-                                          test_url_prefix, relative_path)
-        return 'http://%s:%d%s%s' % (hostname, insecure_port, test_url_prefix,
-                                     relative_path)
-
-    def _get_uri_prefixes(self, hostname, insecure_port, secure_port):
-        """Returns the HTTP and HTTPS URI prefix for a hostname."""
-        return [
-            'http://%s:%d/' % (hostname, insecure_port),
-            'https://%s:%d/' % (hostname, secure_port)
-        ]
-
-    def uri_to_test(self, uri):
-        """Return the base web test name for a given URI.
-
-        This returns the test name for a given URI, e.g., if you passed in
-        "file:///src/web_tests/fast/html/keygen.html" it would return
-        "fast/html/keygen.html".
-        """
-
-        if uri.startswith('file:///'):
-            prefix = path.abspath_to_uri(self._port.host.platform,
-                                         self._port.web_tests_dir())
-            if not prefix.endswith('/'):
-                prefix += '/'
-            return uri[len(prefix):]
-
-        for prefix in self._get_uri_prefixes(*self.HTTP_HOST_AND_PORTS):
-            if uri.startswith(prefix):
-                return self.HTTP_DIR + uri[len(prefix):]
-        for prefix in self._get_uri_prefixes(*self.WPT_HOST_AND_PORTS):
-            if uri.startswith(prefix):
-                url_path = '/' + uri[len(prefix):]
-                for wpt_path, url_prefix in self.WPT_DIRS.items():
-                    if url_path.startswith(url_prefix):
-                        return wpt_path + '/' + url_path[len(url_prefix):]
-        raise NotImplementedError('unknown url type: %s' % uri)
-
     def has_crashed(self):
         if self._server_process is None:
             return False
diff --git a/third_party/blink/tools/blinkpy/wpt_tests/product.py b/third_party/blink/tools/blinkpy/wpt_tests/product.py
index 6e585bef..707a220 100644
--- a/third_party/blink/tools/blinkpy/wpt_tests/product.py
+++ b/third_party/blink/tools/blinkpy/wpt_tests/product.py
@@ -164,6 +164,11 @@
         self.adb_binary = devil_env.config.FetchPath('adb')  # pylint: disable=undefined-variable;
         self.devices = []
 
+    @functools.cached_property
+    def processes(self) -> int:
+        # TODO(crbug.com/333782826): Ensure the derived parallelism is one.
+        return 1
+
     @contextlib.contextmanager
     def _install_apk(self, device, path):
         """Helper context manager for ensuring a device uninstalls an APK."""
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 903a0ab..03ca2f5 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -291,7 +291,6 @@
 crbug.com/1412910 [ Mac ] virtual/view-transition/external/wpt/css/css-view-transitions/fractional-translation-from-transform.html [ Failure ]
 crbug.com/1430357 [ Mac ] virtual/view-transition/external/wpt/css/css-view-transitions/massive-element-* [ Failure Pass ]
 crbug.com/1430357 [ Mac ] virtual/view-transition-wide-gamut/external/wpt/css/css-view-transitions/massive-element-* [ Failure Pass ]
-crbug.com/1430357 [ Win ] virtual/view-transition-wide-gamut/external/wpt/css/css-view-transitions/pseudo-with-classes-* [ Failure ]
 crbug.com/40283765 [ Mac ] virtual/view-transition-on-navigation/wpt_internal/view-transition-on-navigation/transition-to-prerender.html [ Failure ]
 
 # View transition SPA failures with MPA serialization.
@@ -7257,3 +7256,6 @@
 crbug.com/333638402 [ Linux ] external/wpt/css/css-color/parsing/color-computed-lab.html [ Failure ]
 crbug.com/333638402 [ Linux ] external/wpt/css/css-color/parsing/color-valid-color-function.html [ Failure ]
 crbug.com/333638402 [ Linux ] external/wpt/css/css-color/parsing/color-valid-lab.html [ Failure ]
+
+# Gardener 2024-04-11
+crbug.com/333739617 [ Mac ] fast/peerconnection/RTCEncodedVideoFrameMetadata-timestamp.html [ Timeout ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index c2ba011..6a6d5d4 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1872,7 +1872,7 @@
       "external/wpt/shared-storage",
       "http/tests/inspector-protocol/shared-storage"
     ],
-    "args": ["--enable-features=SharedStorageAPI,FencedFrames:implementation_type/mparch,PrivacySandboxAdsAPIsOverride,FencedFramesAPIChanges,FencedFramesDefaultMode,SharedStorageAPIM118,SharedStorageAPIM124,FencedFramesEnforceFocus",
+    "args": ["--enable-features=SharedStorageAPI,FencedFrames:implementation_type/mparch,PrivacySandboxAdsAPIsOverride,FencedFramesAPIChanges,FencedFramesDefaultMode,SharedStorageAPIM118,SharedStorageAPIM124,SharedStorageAPIEnableWALForDatabase,FencedFramesEnforceFocus",
              "--disable-threaded-compositing", "--disable-threaded-animation"],
     "expires": "Jul 31, 2024"
   },
@@ -1885,7 +1885,7 @@
     "exclusive_tests": [
       "external/wpt/shared-storage-selecturl-limit/"
     ],
-    "args": ["--enable-features=SharedStorageAPI,FencedFrames:implementation_type/mparch,FencedFramesAPIChanges,FencedFramesDefaultMode,FencedFramesEnforceFocus,PrivacySandboxAdsAPIsOverride,SharedStorageSelectURLLimit:SharedStorageSelectURLBitBudgetPerPageLoad/9,SharedStorageAPIM118,SharedStorageAPIM124",
+    "args": ["--enable-features=SharedStorageAPI,FencedFrames:implementation_type/mparch,FencedFramesAPIChanges,FencedFramesDefaultMode,FencedFramesEnforceFocus,PrivacySandboxAdsAPIsOverride,SharedStorageSelectURLLimit:SharedStorageSelectURLBitBudgetPerPageLoad/9,SharedStorageAPIM118,SharedStorageAPIM124,SharedStorageAPIEnableWALForDatabase",
              "--disable-threaded-compositing", "--disable-threaded-animation"],
     "expires": "Jul 31, 2024"
   },
diff --git a/third_party/blink/web_tests/WebGPUExpectations b/third_party/blink/web_tests/WebGPUExpectations
index 32a1014..b997050 100644
--- a/third_party/blink/web_tests/WebGPUExpectations
+++ b/third_party/blink/web_tests/WebGPUExpectations
@@ -27,6 +27,12 @@
 crbug.com/40823053 [ Mac ] wpt_internal/webgpu/canvas_webgpu_access/beginWebGPUAccess-texture-readback.https.html [ Skip ]
 crbug.com/40823053 [ Mac ] wpt_internal/webgpu/canvas_webgpu_access/beginWebGPUAccess-texture-readback.https.worker.html [ Skip ]
 
+# Sandbox issue on MacOS < 14.4
+crbug.com/41485470 [ Mac ] wpt_internal/webgpu/canvas_webgpu_access/endWebGPUAccess-canvas-readback-rgba8.https.html [ Failure ]
+crbug.com/41485470 [ Mac ] wpt_internal/webgpu/canvas_webgpu_access/endWebGPUAccess-canvas-readback-rgba8.https.worker.html [ Failure ]
+crbug.com/41485470 [ Mac ] wpt_internal/webgpu/canvas_webgpu_access/endWebGPUAccess-canvas-readback-rgba16f.https.html [ Failure ]
+crbug.com/41485470 [ Mac ] wpt_internal/webgpu/canvas_webgpu_access/endWebGPUAccess-canvas-readback-rgba16f.https.worker.html [ Failure ]
+
 # Linux does not yet properly support readback on shared images.
 crbug.com/40218893 [ Linux ] wpt_internal/webgpu/canvas_webgpu_access/beginWebGPUAccess-texture-readback.https.html [ Skip ]
 crbug.com/40218893 [ Linux ] wpt_internal/webgpu/canvas_webgpu_access/beginWebGPUAccess-texture-readback.https.worker.html [ Skip ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/parsing/position-visibility-computed.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/parsing/position-visibility-computed.tentative.html
index ff4ceb7..8a8ba88 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/parsing/position-visibility-computed.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/parsing/position-visibility-computed.tentative.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>CSS Anchor Positioning Test: Computed position-visibility</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/css/support/computed-testcommon.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/parsing/position-visibility-parsing.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/parsing/position-visibility-parsing.tentative.html
index 18dd27ead..942ec71 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/parsing/position-visibility-parsing.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/parsing/position-visibility-parsing.tentative.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>CSS Anchor Positioning Test: Parsing of position-visibility</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/css/support/parsing-testcommon.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-add-no-overflow.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-add-no-overflow.html
similarity index 91%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-add-no-overflow.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-add-no-overflow.html
index 9d87f82..de0647f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-add-no-overflow.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-add-no-overflow.html
@@ -2,7 +2,7 @@
 <html class=reftest-wait>
 <meta charset="utf-8">
 <title>CSS Anchor Positioning Test: position-visibility: no-overflow</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-no-overflow-ref.html">
 <style>
   #scroll-container {
@@ -44,4 +44,4 @@
   });
 });
 </script>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-valid.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-valid.tentative.html
index bf67921..4b069c2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-valid.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-valid.tentative.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>CSS Anchor Positioning Test: position-visibility: anchors-valid</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-anchors-valid-ref.html">
 <style>
   .anchor {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.html
similarity index 94%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.html
index cea439c..f13c500 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.html
@@ -3,7 +3,7 @@
 <meta charset="utf-8">
 <meta name="assert" content="Scrolling an anchor in to view should cause a position-visibility: anchors-visible element to appear." />
 <title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-anchors-visible-after-scroll-in-ref.html">
 <script src="/common/reftest-wait.js"></script>
 <script src="/common/rendering-utils.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.html
similarity index 93%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.html
index b2e3643..4294091 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.html
@@ -3,7 +3,7 @@
 <meta charset="utf-8">
 <meta name="assert" content="Scrolling an anchor out of view should cause a position-visibility: anchors-visible element to disappear." />
 <title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-anchors-visible-after-scroll-out-ref.html">
 <script src="/common/reftest-wait.js"></script>
 <script src="/common/rendering-utils.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.html
similarity index 94%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.html
index f8b1cc6..117628e7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.html
@@ -3,7 +3,7 @@
 <meta charset="utf-8">
 <meta name="assert" content="Position-visibility should not be affected by the visibility of a previous anchor." />
 <title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-anchors-visible-change-anchor-ref.html">
 <script src="/common/reftest-wait.js"></script>
 <script src="/common/rendering-utils.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-css-visibility.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-css-visibility.html
similarity index 93%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-css-visibility.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-css-visibility.html
index 22a3065..f9c5983 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-css-visibility.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-change-css-visibility.html
@@ -3,7 +3,7 @@
 <meta charset="utf-8">
 <meta name="assert" content="Position-visibility: anchors-visible should show an element after an anchor changes from visibility: hidden to visibility: visible." />
 <title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-anchors-visible-change-css-visibility-ref.html">
 <script src="/common/reftest-wait.js"></script>
 <script src="/common/rendering-utils.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-css-visibility.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-css-visibility.html
similarity index 89%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-css-visibility.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-css-visibility.html
index 31be797..a699025 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-css-visibility.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-css-visibility.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <meta name="assert" content="Position-visibility: anchors-visible should hide an element with an anchor that has visibility: hidden." />
 <title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-anchors-visible-css-visibility-ref.html">
 <style>
   #container {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.html
similarity index 94%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.html
index 7b84976..9c4d085b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <meta name="assert" content="position-visibility: anchors-visible should consider the visibility of the anchor relative the containing scroller, ignoring visibility in other scrollers." />
 <title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-anchors-visible-non-intervening-container-ref.html">
 <style>
   #non-intervening-scroll-container {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-stacked-child.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-stacked-child.html
similarity index 93%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-stacked-child.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-stacked-child.html
index 7c0d5dc..e563fec 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-stacked-child.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-stacked-child.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <meta name="assert" content="Position-visibility: anchors-visible should hide an element and stacked children with an out-of-view anchor." />
 <title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-anchors-visible-ref.html">
 <style>
   #scroll-container {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-with-position.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-with-position.html
similarity index 92%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-with-position.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-with-position.html
index 82eed0b..43dd2cc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-with-position.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible-with-position.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <meta name="assert" content="Position-visibility: anchors-visible should hide an element with an out-of-view anchor and a relpos scroller." />
 <title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-anchors-visible-ref.html">
 <style>
   #scroll-container {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible.html
similarity index 91%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible.html
index 85b8d89..78daffb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-anchors-visible.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <meta name="assert" content="Position-visibility: anchors-visible should hide an element with an out-of-view anchor." />
 <title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-anchors-visible-ref.html">
 <style>
   #scroll-container {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-scroll.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-scroll.html
similarity index 92%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-scroll.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-scroll.html
index 4751faeb..f646f81 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-scroll.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-scroll.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html class="reftest-wait">
 <title>CSS Anchor Positioning Test: position-visibility: no-overflow</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-no-overflow-scroll-ref.html">
 <style>
   #scroll-container {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-stacked-child.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-stacked-child.html
similarity index 92%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-stacked-child.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-stacked-child.html
index f748fda..1ea5ff9a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-stacked-child.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-stacked-child.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>CSS Anchor Positioning Test: position-visibility: no-overflow</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-no-overflow-ref.html">
 <style>
   #scroll-container {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow.html
similarity index 90%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow.html
index 39fb55b1..ea3b2d0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>CSS Anchor Positioning Test: position-visibility: no-overflow</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-no-overflow-ref.html">
 <style>
   #scroll-container {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-anchors-visible.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-anchors-visible.html
similarity index 93%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-anchors-visible.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-anchors-visible.html
index c6649e5f..95be15c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-anchors-visible.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-anchors-visible.html
@@ -3,7 +3,7 @@
 <meta charset="utf-8">
 <meta name="assert" content="Removing position-visibility: anchors-visible from an invisible anchored element should cause it to become visible." />
 <title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-remove-anchors-visible-ref.html">
 <script src="/common/reftest-wait.js"></script>
 <script src="/common/rendering-utils.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-no-overflow.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-no-overflow.html
similarity index 91%
rename from third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-no-overflow.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-no-overflow.html
index a043917..2cd2ed9f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-no-overflow.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-remove-no-overflow.html
@@ -2,7 +2,7 @@
 <html class=reftest-wait>
 <meta charset="utf-8">
 <title>CSS Anchor Positioning Test: position-visibility: no-overflow</title>
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
 <link rel="match" href="position-visibility-no-overflow-ref.html">
 <style>
   #scroll-container {
@@ -45,4 +45,4 @@
   });
 });
 </script>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/gamepad/gamepad-dual-rumble-effect-manual.https.html b/third_party/blink/web_tests/external/wpt/gamepad/gamepad-dual-rumble-effect-manual.https.html
new file mode 100644
index 0000000..4a1c5ba8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/gamepad/gamepad-dual-rumble-effect-manual.https.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title></title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <p>
+      This test requires a gamepad device to be connected. Please interact with
+      the gamepad for it to be recognized. The "Dual rumble!" button will be
+      enabled after that.
+    </p>
+    <p>
+      After pressing the "Dual rumble!" button below, you should expect all the
+      "dual-rumble" compatible gamepads to vibrate for one second.
+    </p>
+    <p>
+      Please press the "Confirm effect has played" button to conclude
+      the test.
+    </p>
+    <button id="play_dual_rumble_button" disabled>No dual-rumble gamepads detected</button>
+    <button id="confirm_effect_button" disabled>Confirm effect has played</button>
+    <script>
+      async_test(t => {
+        let connectedDualRumbleGamepads = {};
+        playEffectButton = document.getElementById('play_dual_rumble_button');
+
+        function isDualRumbleSupported(gamepad) {
+          return gamepad.vibrationActuator.effects.includes('dual-rumble');
+        }
+
+        window.addEventListener('gamepadconnected', (e) => {
+          if (!e.gamepad || !e.gamepad.vibrationActuator || !e.gamepad.vibrationActuator.effects) {
+            return;
+          }
+
+          if (isDualRumbleSupported(e.gamepad)) {
+            connectedDualRumbleGamepads[e.gamepad.index] = e.gamepad;
+
+            if (playEffectButton.disabled) {
+              playEffectButton.disabled = false;
+              playEffectButton.innerText = 'Dual rumble!'
+            }
+          }
+        });
+
+        window.addEventListener('gamepaddisconnected', (e) => {
+          delete connectedDualRumbleGamepads[e.gamepad.index];
+
+          let anyDualRumbleGamepad = false;
+          for (let index in connectedDualRumbleGamepads){
+            const gamepad = connectedDualRumbleGamepads[index];
+            if (!gamepad || !gamepad.vibrationActuator || !gamepad.vibrationActuator.effects) {
+              continue;
+            }
+
+            if (isDualRumbleSupported(gamepad)){
+              anyDualRumbleGamepad = true;
+              break;
+            }
+          }
+
+          if (!anyDualRumbleGamepad && !playEffectButton.disabled) {
+            playEffectButton.disabled = true;
+            playEffectButton.innerText = "No dual-rumble gamepads detected";
+          }
+        });
+
+        playEffectButton.addEventListener("click", () => {
+          let gamepads = navigator.getGamepads();
+          for (const gamepad of gamepads) {
+            if (gamepad && isDualRumbleSupported(gamepad)) {
+              gamepad.vibrationActuator.playEffect("dual-rumble", {
+                duration: 1000,
+                strongMagnitude: 1.0,
+                weakMagnitude: 1.0,
+              });
+            }
+          }
+
+          const confirmButton = document.getElementById("confirm_effect_button");
+          if (confirmButton.disabled) {
+            confirmButton.disabled = false;
+          }
+          confirmButton.addEventListener('click', () => {
+            t.done();
+          });
+        });
+      }, "Gamepads with dual-rumble capabilities should have the body's motors activated.");
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/gamepad/gamepad-trigger-rumble-effect-manual.https.html b/third_party/blink/web_tests/external/wpt/gamepad/gamepad-trigger-rumble-effect-manual.https.html
new file mode 100644
index 0000000..f436a60
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/gamepad/gamepad-trigger-rumble-effect-manual.https.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title></title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <p>
+      This test requires a gamepad device to be connected. Please interact with
+      the gamepad for it to be recognized. The "Trigger rumble!" button will be
+      enabled after that.
+    </p>
+    <p>
+      After pressing the "Trigger rumble!" button below, you should expect to
+      feel a localized vibration in both triggers of all connected
+      trigger-rumble compatible gamepads for one second.
+    </p>
+    <p>
+      Please press the "Confirm effect has played" button to conclude
+      the test.
+    </p>
+    <button id="play_trigger_rumble_button" disabled>No trigger-rumble gamepads detected</button>
+    <button id="confirm_effect_button" disabled>Confirm effect has played</button>
+    <script>
+      async_test(t => {
+        let connectedTriggerRumbleGamepads = {};
+        playEffectButton = document.getElementById('play_trigger_rumble_button');
+
+        function isTriggerRumbleSupported(gamepad) {
+          return gamepad.vibrationActuator.effects.includes('trigger-rumble');
+        }
+
+        window.addEventListener('gamepadconnected', (e) => {
+          if (!e.gamepad || !e.gamepad.vibrationActuator || !e.gamepad.vibrationActuator.effects) {
+            return;
+          }
+
+          if (isTriggerRumbleSupported(e.gamepad)) {
+            connectedTriggerRumbleGamepads[e.gamepad.index] = e.gamepad;
+
+            if (playEffectButton.disabled) {
+              playEffectButton.disabled = false;
+              playEffectButton.innerText = 'Trigger rumble!'
+            }
+          }
+        });
+
+        window.addEventListener('gamepaddisconnected', (e) => {
+          delete connectedTriggerRumbleGamepads[e.gamepad.index];
+
+          let anyTriggerRumbleGamepad = false;
+          for (let index in connectedTriggerRumbleGamepads){
+            const gamepad = connectedTriggerRumbleGamepads[index];
+            if (!gamepad || !gamepad.vibrationActuator || !gamepad.vibrationActuator.effects) {
+              continue;
+            }
+
+            if (isTriggerRumbleSupported(gamepad)){
+              anyTriggerRumbleGamepad = true;
+              break;
+            }
+          }
+
+          if (!anyTriggerRumbleGamepad && !playEffectButton.disabled) {
+            playEffectButton.disabled = true;
+            playEffectButton.innerText = "No trigger-rumble gamepads detected";
+          }
+        });
+
+        playEffectButton.addEventListener("click", () => {
+          let gamepads = navigator.getGamepads();
+          for (const gamepad of gamepads) {
+            if (gamepad && isTriggerRumbleSupported(gamepad)) {
+              gamepad.vibrationActuator.playEffect("trigger-rumble", {
+                duration: 1000,
+                leftTrigger: 1.0,
+                rightTrigger: 1.0,
+              });
+            }
+          }
+
+          const confirmButton = document.getElementById("confirm_effect_button");
+          if (confirmButton.disabled) {
+            confirmButton.disabled = false;
+          }
+          confirmButton.addEventListener('click', () => {
+            t.done();
+          });
+        });
+      }, "Gamepads with trigger-rumble capabilities should have the triggers' motors activated.");
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/native-popup-with-datalist-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/native-popup-with-datalist-ref.html
new file mode 100644
index 0000000..87918b6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/native-popup-with-datalist-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<select>
+  <option>one</option>
+  <hr>
+  <option>two</option>
+</select>
+
+<script>
+(async () => {
+  await test_driver.click(document.querySelector('select'));
+  document.documentElement.classList.remove('reftest-wait');
+})();
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/native-popup-with-datalist.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/native-popup-with-datalist.tentative.html
new file mode 100644
index 0000000..a968c6a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/native-popup-with-datalist.tentative.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/9799">
+<link rel=match href="native-popup-with-datalist-ref.html">
+<link rel=assert title="The native popup of a select should show options descending from datalists">
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<select>
+  <datalist>
+    <div>
+      <option>one</option>
+      <hr>
+      <option>two</option>
+    </div>
+  </datalist>
+</select>
+
+<script>
+(async () => {
+  await test_driver.click(document.querySelector('select'));
+  document.documentElement.classList.remove('reftest-wait');
+})();
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/nested-options.tenative.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/nested-options.tenative.html
new file mode 100644
index 0000000..7e89a5ad
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/nested-options.tenative.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1422275">
+<link rel=help href="https://chromium-review.googlesource.com/c/chromium/src/+/5441435/1#message-cd8841d92a672a0276ab536dfaf3a20e93d5e6e3">
+<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>
+
+<select>
+  <datalist>
+    <option id=o1>
+      parent
+      <option id=o2>child</option>
+</option>
+  </datalist>
+</select>
+
+<script>
+const select = document.querySelector('select');
+
+test(() => {
+  assert_equals(select.innerHTML, `
+  <datalist>
+    <option id="o1">
+      parent
+      </option><option id="o2">child</option>
+
+  </datalist>
+`);
+}, 'The HTML parser should disallow nested options in select datalist.');
+
+// Manually nest the <options> anyway for the following tests.
+o1.appendChild(o2);
+
+test(() => {
+  assert_equals(select.options.length, 2, 'select.options.length');
+  assert_equals(select.options[0], o1, 'select.options[0]');
+  assert_equals(select.options[1], o2, 'select.options[1]');
+}, 'Nested <options> should be listed in <select> IDLs.');
+
+promise_test(async () => {
+  await test_driver.bless();
+  select.showPicker();
+}, 'Showing the popup with nested <option>s should not crash.');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/resources/select-reset-non-interoperable-styles.css b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/resources/select-reset-non-interoperable-styles.css
deleted file mode 100644
index d2b9d9df..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/resources/select-reset-non-interoperable-styles.css
+++ /dev/null
@@ -1,5 +0,0 @@
-/* TODO(crbug.com/1511354): linux.css sets background-color on select, consider
- * removing it. */
-select {
-  background-color: Field;
-}
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/resources/stylable-select-styles.css b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/resources/stylable-select-styles.css
index 042de83..5902d9e 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/resources/stylable-select-styles.css
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/resources/stylable-select-styles.css
@@ -1,7 +1,6 @@
 /* These are UA styles for select and stylable select. */
 
 .stylable-select-container {
-  background-color: Field;
   border: 1px solid rgba(0, 0, 0, 0);
   border-radius: 0;
   box-sizing: border-box;
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/select-child-button-and-datalist-invalidation.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/select-child-button-and-datalist-invalidation.tentative.html
index b6d85ac9..822a63e1 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/select-child-button-and-datalist-invalidation.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/select-child-button-and-datalist-invalidation.tentative.html
@@ -3,7 +3,6 @@
 <link rel=author href="mailto:jarhar@chromium.org">
 <link rel=help href="https://github.com/whatwg/html/issues/9799">
 <link rel=match href="select-child-button-and-datalist-ref.html">
-<link rel=stylesheet href="resources/select-reset-non-interoperable-styles.css">
 
 <style>
 .blue {
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/select-child-button-and-datalist.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/select-child-button-and-datalist.tentative.html
index 610861a..9b2f53d 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/select-child-button-and-datalist.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/stylable-select/select-child-button-and-datalist.tentative.html
@@ -2,7 +2,6 @@
 <link rel=author href="mailto:jarhar@chromium.org">
 <link rel=help href="https://github.com/whatwg/html/issues/9799">
 <link rel=match href="select-child-button-and-datalist-ref.html">
-<link rel=stylesheet href="resources/select-reset-non-interoperable-styles.css">
 
 <style>
 .blue {
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-streams/BrowserCaptureMediaStreamTrack-restrictTo.https.html b/third_party/blink/web_tests/external/wpt/mediacapture-streams/BrowserCaptureMediaStreamTrack-restrictTo.https.html
new file mode 100644
index 0000000..4b0da74
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mediacapture-streams/BrowserCaptureMediaStreamTrack-restrictTo.https.html
@@ -0,0 +1,245 @@
+<!doctype html>
+<html>
+
+<head>
+  <title>BrowserCaptureMediaStreamTrack restrictTo()</title>
+  <link rel="help" href="https://screen-share.github.io/element-capture/">
+</head>
+
+<body>
+  <p class="instructions">
+    When prompted, accept to give permission to use your audio, video devices.
+  </p>
+  <h1 class="instructions">Description</h1>
+  <p class="instructions">
+    This test checks that restricting BrowserCaptureMediaStreamTrack works as
+    expected.
+  </p>
+
+  <style>
+    div {
+      height: 100px;
+    }
+    .stacking {
+      opacity: 0.9;
+    }
+    #container {
+      columns:4;
+      column-fill:auto;
+    }
+    .fragmentize {
+      height: 50px;
+    }
+    #target {
+      background: linear-gradient(red, blue);
+    }
+  </style>
+
+
+  <div id='container'>
+    <div id='target'></div>
+  </div>
+  <video id="video"
+         style="border: 2px blue dotted; width: 250px; height: 250px;"
+         autoplay playsinline muted></video>
+
+  <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>
+    "use strict";
+
+    // For more information, see:
+    // https://screen-share.github.io/element-capture/#elements-eligible-for-restriction
+    const EligibilityRequirement = {
+      StackingContext: "StackingContext",
+      OnlyOneBoxFragment: "OnlyOneBoxFragment",
+      FlattenedIn3D: "FlattenedIn3D",
+    };
+
+    // The target div.
+    const div = document.getElementById('target');
+
+    // Returns a promise that, if successful, will resolve to a media stream.
+    async function getDisplayMedia() {
+      return test_driver.bless('getDisplayMedia', () =>
+        navigator.mediaDevices.getDisplayMedia({
+          video: { displaySurface: "browser" },
+          selfBrowserSurface: "include",
+        }));
+    }
+
+    // Returns a promise that will resolve successfully if at least one frame is
+    // read before the timeout.
+    function assertFrameRead(t, state, message) {
+      const last_frame_count = state.frame_count;
+      return t.step_wait(() => state.frame_count > last_frame_count,
+        message, 5000, 10);
+    }
+
+    // Returns a promise that will resolve successfully if there are no frames
+    // produced for an entire second after being called.
+    function assertStopsProducingFrames(t, state, message) {
+      let last_frame_count = state.frame_count;
+
+      return t.step_timeout(() => {
+        assert_equals(state.frame_count, last_frame_count);
+      }, 1000);
+    }
+
+    function makeDivEligible() {
+      // Must always have a stacking context to be eligible.
+      div.classList.add("stacking");
+      div.parentElement.classList.remove("fragmented");
+      div.style.transform = "";
+    }
+
+    function makeDivIneligible(state) {
+      switch(state.eligibilityParam) {
+        case EligibilityRequirement.StackingContext:
+          div.classList.remove("stacking");
+          break;
+
+        case EligibilityRequirement.OnlyOneBoxFragment:
+          div.parentElement.classList.add("fragmented");
+          break;
+
+        case EligibilityRequirement.FlattenedIn3D:
+          div.style.transform = "rotateY(90deg)";
+          break;
+      }
+    }
+
+    // Restore element state after each test.
+    function cleanupDiv() {
+      div.classList.remove("stacking");
+      div.parentElement.classList.remove("fragmented");
+      div.style.transform = "";
+    }
+
+    function startAnimation(t, state) {
+      let count = 0;
+      function animate() {
+        if (!state.running) {
+          return;
+        }
+        count += 1;
+        div.innerText = count;
+        window.requestAnimationFrame(animate);
+      }
+      window.requestAnimationFrame(animate);
+
+      // Stop animation as part of cleanup.
+      t.add_cleanup(() => { state.running = false; });
+    }
+
+    // Updates the state.frame_count value whenever a new frame is received on
+    // the passed in media stream track.
+    async function readFromTrack(state, track) {
+      while (state.running) {
+        const reader = new MediaStreamTrackProcessor(track).readable.getReader();
+        while (true) {
+          const frameOrDone = await reader.read();
+          if (frameOrDone.done) {
+            break;
+          }
+          frameOrDone.value.close();
+          state.frame_count += 1;
+        }
+      }
+    }
+
+    // Parameterized test method. Note that this returns a Promise that will be
+    // resolved to represent success of the entire promise test.
+    async function runTest(t, eligibilityParam) {
+      let state = {
+        eligibilityParam: eligibilityParam,
+        frame_count: 0,
+        running: true,
+        reading: false,
+      };
+      startAnimation(t, state);
+
+      let videoTrack = undefined;
+      return getDisplayMedia().then(stream => {
+        t.add_cleanup(() => {
+          stream.getTracks().forEach(track => track.stop());
+        });
+        assert_true(!!stream, "should have resolved to a stream.");
+        assert_true(stream.active, "stream should be active.");
+        assert_equals(stream.getVideoTracks().length, 1);
+
+        [videoTrack] = stream.getVideoTracks();
+        assert_true(videoTrack instanceof MediaStreamTrack,
+          "track should be either MediaStreamTrack or a subclass thereof.");
+        assert_equals(videoTrack.readyState, "live", "track should be live.");
+
+        // Consume the stream in a video element.
+        const video = document.querySelector('video');
+        video.srcObject = stream;
+
+        // Remove the video source, so that the stream object can be released.
+        t.add_cleanup(() => {video.srcObject = null});
+
+        // Keep track of the number of frames used.
+        const readPromise = readFromTrack(state, videoTrack);
+        t.add_cleanup(() => readPromise);
+
+        return assertFrameRead(t, state, "Track should produce frames.");
+      }).then(() => {
+        assert_true(!!RestrictionTarget, "RestrictionTarget exposed.");
+        assert_true(!!RestrictionTarget.fromElement,
+          "RestrictionTarget.fromElement exposed.");
+
+        return RestrictionTarget.fromElement(div);
+      }).then(restrictionTarget => {
+        assert_true(!!videoTrack.restrictTo, "restrictTo exposed.");
+        assert_true(typeof videoTrack.restrictTo === 'function',
+          "restrictTo is a function.");
+
+        return videoTrack.restrictTo(restrictionTarget);
+      }).then(() => {
+        // By default, elements are not eligible for restriction due to not being
+        // placed in their own stacking context.
+        return assertStopsProducingFrames(t, state,
+          "No new frames after restriction.");
+      });
+
+    // TODO(crbug.com/333770107): once the issue with the
+    // --disable-threaded-compositing flag is resolved on Chrome's check in bots
+    // the rest of this test should be enabled.
+    //   ).then(() => {
+    //     // Should be unpaused now that the element is eligible.
+    //     makeDivEligible();
+
+    //     // Make sure the element state is restored to default between tests.
+    //     t.add_cleanup(() => { cleanupDiv(); });
+
+    //     // Restart the reader now that the stream is producing frames again.
+    //     return assertFrameRead(t, state,
+    //       "Received at least one frame after becoming eligible.");
+    //   }).then(() => {
+
+    //     // Should pause if it becomes ineligible again.
+    //     makeDivIneligible(state);
+    //     return assertStopsProducingFrames(t, state,
+    //       "No new frames after becoming ineligible again.");
+    //   });
+    }
+
+    // Test parameterizations.
+    [
+      EligibilityRequirement.StackingContext,
+      EligibilityRequirement.OnlyOneBoxFragment,
+      EligibilityRequirement.FlattenedIn3D,
+    ].forEach(param =>
+      promise_test(t => runTest(t, param),
+        `Tests that restricting MediaStreamTrack objects works as expected (${param}).`
+    ));
+
+  </script>
+</body>
+
+</html>
diff --git a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/gather.https.any-expected.txt b/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/gather.https.any-expected.txt
deleted file mode 100644
index ea4fa26..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/gather.https.any-expected.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] gather float32 1D tensor and uint32 0D scalar indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 1D tensor and int64 0D scalar indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 1D tensor and int64 1D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 1D tensor and int64 2D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 1D tensor and int64 3D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 1D tensor and int64 4D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 2D tensor and 0D scalar indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 2D tensor and 1D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 2D tensor and 2D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 2D tensor and 3D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 2D tensor and 4D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 3D tensor and 2D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 4D tensor and 2D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 5D tensor and 1D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 3D tensor and 1D indices options.axis=1
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 3D tensor and 2D indices options.axis=2
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 4D tensor and 2D indices explict options.axis=0
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 5D tensor and 0D scalar indices options.axis=4
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/gather.https.any.worker-expected.txt b/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/gather.https.any.worker-expected.txt
deleted file mode 100644
index ea4fa26..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/gather.https.any.worker-expected.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] gather float32 1D tensor and uint32 0D scalar indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 1D tensor and int64 0D scalar indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 1D tensor and int64 1D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 1D tensor and int64 2D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 1D tensor and int64 3D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 1D tensor and int64 4D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 2D tensor and 0D scalar indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 2D tensor and 1D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 2D tensor and 2D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 2D tensor and 3D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 2D tensor and 4D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 3D tensor and 2D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 4D tensor and 2D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 5D tensor and 1D indices default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 3D tensor and 1D indices options.axis=1
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 3D tensor and 2D indices options.axis=2
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 4D tensor and 2D indices explict options.axis=0
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-[FAIL] gather float32 5D tensor and 0D scalar indices options.axis=4
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': gather is not implemented"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/stylable-select-disabled/external/wpt/html/semantics/forms/the-select-element/stylable-select/nested-options.tenative-expected.txt b/third_party/blink/web_tests/virtual/stylable-select-disabled/external/wpt/html/semantics/forms/the-select-element/stylable-select/nested-options.tenative-expected.txt
new file mode 100644
index 0000000..65363766
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/stylable-select-disabled/external/wpt/html/semantics/forms/the-select-element/stylable-select/nested-options.tenative-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+[FAIL] The HTML parser should disallow nested options in select datalist.
+  assert_equals: expected "\\n  <datalist>\\n    <option id=\\"o1\\">\\n      parent\\n      </option><option id=\\"o2\\">child</option>\\n\\n  </datalist>\\n" but got "\\n  \\n    <option id=\\"o1\\">\\n      parent\\n      </option><option id=\\"o2\\">child</option>\\n\\n  \\n"
+[FAIL] Nested <options> should be listed in <select> IDLs.
+  assert_equals: select.options.length expected 2 but got 1
+Harness: the test ran to completion.
+
diff --git a/third_party/catapult b/third_party/catapult
index 6e7b54b..24c482a 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit 6e7b54bc42e32e640cf79e1ae4af0716bac2742c
+Subproject commit 24c482ad9c93a7e6504c70ed8a7f1716543c8371
diff --git a/third_party/chromite b/third_party/chromite
index 9369805..004355d 160000
--- a/third_party/chromite
+++ b/third_party/chromite
@@ -1 +1 @@
-Subproject commit 9369805bd6c6b5f59a92b3a4106eb691594eb06b
+Subproject commit 004355d6885f0d4735fea67a97761604c85a9940
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index d64e9e2..4958ce3 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit d64e9e23c59862c4c7fd4a79bb770934dbce6e64
+Subproject commit 4958ce329838edce2c9ddd42f8a0e1cfbaf6ec35
diff --git a/third_party/dawn b/third_party/dawn
index 1c970ba..ac32331 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 1c970ba163189abfd5e0df373cdd57c0595e3be6
+Subproject commit ac32331dc38a0d78bc2a7fd0e89a8e30db10d4ed
diff --git a/third_party/depot_tools b/third_party/depot_tools
index c41b0af..7894b0d 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit c41b0affa0aacd0e8b332477e3f46b579d7672b6
+Subproject commit 7894b0d6811036f55f472784d8dd86640450ac41
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index 1ebb03b..7941d99 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit 1ebb03b579ea72f1269fbd72f71067593aadd274
+Subproject commit 7941d991440a39b32121ef850dbed0f815cff41c
diff --git a/third_party/fp16/README.chromium b/third_party/fp16/README.chromium
index 4ab16a3..ab46572c2 100644
--- a/third_party/fp16/README.chromium
+++ b/third_party/fp16/README.chromium
@@ -1,8 +1,8 @@
 Name: FP16
 Short Name: fp16
 URL: https://github.com/Maratyszcza/FP16
-Version: 0a92994d729ff76a58f692d3028ca1b64b145d91
-Date: 2022/10/24
+Version: 581ac1c79dd9d9f6f4e8b2934e7a55c7becf0799
+Date: 2024-04-10
 License: MIT
 License File: LICENSE
 Security Critical: Yes
diff --git a/third_party/fp16/src b/third_party/fp16/src
index 0a92994..581ac1c 160000
--- a/third_party/fp16/src
+++ b/third_party/fp16/src
@@ -1 +1 @@
-Subproject commit 0a92994d729ff76a58f692d3028ca1b64b145d91
+Subproject commit 581ac1c79dd9d9f6f4e8b2934e7a55c7becf0799
diff --git a/third_party/jni_zero/BUILD.gn b/third_party/jni_zero/BUILD.gn
index 0950f96..c4f059ad 100644
--- a/third_party/jni_zero/BUILD.gn
+++ b/third_party/jni_zero/BUILD.gn
@@ -57,7 +57,7 @@
     proguard_configs = [ "proguard.flags" ]
   }
 
-  group("jni_generator_tests") {
+  group("jni_zero_tests") {
     testonly = true
     deps = [
       "//third_party/jni_zero/sample:jni_zero_sample_apk_test",
diff --git a/third_party/lens_server_proto/README.chromium b/third_party/lens_server_proto/README.chromium
index e36c2ed..ef83d2c 100644
--- a/third_party/lens_server_proto/README.chromium
+++ b/third_party/lens_server_proto/README.chromium
@@ -1,8 +1,8 @@
 Name: Lens Protos
 Short Name: lens_overlay_proto
 URL: This is the canonical public repository
-Version: 622219474
-Date: 2024-04-05 UTC
+Version: 623362227
+Date: 2024-04-10 UTC
 License: BSD
 License File: LICENSE
 Shipped: yes
diff --git a/third_party/lens_server_proto/lens_overlay_overlay_object.proto b/third_party/lens_server_proto/lens_overlay_overlay_object.proto
index afcb493..6b54af6 100644
--- a/third_party/lens_server_proto/lens_overlay_overlay_object.proto
+++ b/third_party/lens_server_proto/lens_overlay_overlay_object.proto
@@ -18,5 +18,17 @@
   // The object geometry.
   Geometry geometry = 2;
 
-  reserved 3, 4, 5, 6, 7, 8;
+  // Rendering metadata for the object.
+  message RenderingMetadata {
+    enum RenderType {
+      DEFAULT = 0;
+      GLEAM = 1;
+    }
+    RenderType render_type = 1;
+  }
+
+  // The rendering metadata for the object.
+  RenderingMetadata rendering_metadata = 8;
+
+  reserved 3, 4, 5, 6, 7;
 }
diff --git a/third_party/leveldatabase/env_chromium.cc b/third_party/leveldatabase/env_chromium.cc
index 3c11fd43..8c7af49 100644
--- a/third_party/leveldatabase/env_chromium.cc
+++ b/third_party/leveldatabase/env_chromium.cc
@@ -1092,14 +1092,14 @@
     } else {
       NOTREACHED();
     }
-    tracker_->DatabaseOpened(this, shared_read_cache_use_);
+    tracker_->DatabaseOpened(this);
   }
 
   TrackedDBImpl(const TrackedDBImpl&) = delete;
   TrackedDBImpl& operator=(const TrackedDBImpl&) = delete;
 
   ~TrackedDBImpl() override {
-    tracker_->DatabaseDestroyed(this, shared_read_cache_use_);
+    tracker_->DatabaseDestroyed(this);
     db_.reset();
   }
 
@@ -1351,15 +1351,13 @@
     visitor.Run(i->value());
 }
 
-void DBTracker::DatabaseOpened(TrackedDBImpl* database,
-                               SharedReadCacheUse cache_use) {
+void DBTracker::DatabaseOpened(TrackedDBImpl* database) {
   base::AutoLock lock(databases_lock_);
   databases_.Append(database);
   mdp_->DatabaseOpened(database);
 }
 
-void DBTracker::DatabaseDestroyed(TrackedDBImpl* database,
-                                  SharedReadCacheUse cache_use) {
+void DBTracker::DatabaseDestroyed(TrackedDBImpl* database) {
   base::AutoLock lock(databases_lock_);
   mdp_->DatabaseDestroyed(database);
   database->RemoveFromList();
@@ -1380,7 +1378,11 @@
   if (options.env && leveldb_chrome::IsMemEnv(options.env)) {
     Options mem_options = options;
     mem_options.block_cache = leveldb_chrome::GetSharedInMemoryBlockCache();
-    mem_options.write_buffer_size = 0;  // minimum size.
+    // Minimum size to save memory and because writing is cheap.
+    mem_options.write_buffer_size = 0;
+    // All data is stored in memory so there's no cost to holding a "file" open.
+    mem_options.max_open_files = std::numeric_limits<int>::max();
+    mem_options.create_if_missing = true;
     s = DBTracker::GetInstance()->OpenDatabase(mem_options, name, &tracked_db);
   } else {
     std::string tmp_name = DatabaseNameForRewriteDB(name);
@@ -1424,9 +1426,9 @@
 leveldb::Status RewriteDB(const leveldb_env::Options& options,
                           const std::string& name,
                           std::unique_ptr<leveldb::DB>* dbptr) {
-  DCHECK(options.create_if_missing);
   if (leveldb_chrome::IsMemEnv(options.env))
     return Status::OK();
+  DCHECK(options.create_if_missing);
   TRACE_EVENT1("leveldb", "ChromiumEnv::RewriteDB", "name", name);
   leveldb::Status s;
   std::string tmp_name = DatabaseNameForRewriteDB(name);
diff --git a/third_party/leveldatabase/env_chromium.h b/third_party/leveldatabase/env_chromium.h
index 0c2c003..ecda6d4 100644
--- a/third_party/leveldatabase/env_chromium.h
+++ b/third_party/leveldatabase/env_chromium.h
@@ -282,8 +282,8 @@
   // Checks if |db| is tracked.
   bool IsTrackedDB(const leveldb::DB* db) const;
 
-  void DatabaseOpened(TrackedDBImpl* database, SharedReadCacheUse cache_use);
-  void DatabaseDestroyed(TrackedDBImpl* database, SharedReadCacheUse cache_use);
+  void DatabaseOpened(TrackedDBImpl* database);
+  void DatabaseDestroyed(TrackedDBImpl* database);
 
   // Protect databases_ and mdp_ members.
   mutable base::Lock databases_lock_;
@@ -297,9 +297,9 @@
 //   1. |dbptr| is not touched on failure
 //   2. |dbptr| is not NULL on success
 //
-// Note: All |options| values are honored, except if options.env is an in-memory
-// Env. In this case the block cache is disabled and a minimum write buffer size
-// is used to conserve memory with all other values honored.
+// Note that some `options` may not be honored, for example in the case of
+// in-memory databases, the block cache is disabled and a minimum write buffer
+// size is used to conserve memory.
 LEVELDB_EXPORT leveldb::Status OpenDB(const leveldb_env::Options& options,
                                       const std::string& name,
                                       std::unique_ptr<leveldb::DB>* dbptr);
diff --git a/third_party/leveldatabase/leveldb_chrome.cc b/third_party/leveldatabase/leveldb_chrome.cc
index 1cdd54ae5..eda4c79 100644
--- a/third_party/leveldatabase/leveldb_chrome.cc
+++ b/third_party/leveldatabase/leveldb_chrome.cc
@@ -286,6 +286,32 @@
   return result;
 }
 
+// This is an empty `Cache`, suitable for use as a block cache for in-memory
+// databases. It will not function in contexts where `Cache` is expected to
+// behave consistently, i.e. where `Insert()` is expected to return a non-null
+// value that can be handled by `Lookup()` or `Value()`. The block cache in
+// particular does not have this requirement.
+class NoOpBlockCache : public Cache {
+ public:
+  NoOpBlockCache() = default;
+  ~NoOpBlockCache() override = default;
+
+  Handle* Insert(const leveldb::Slice& key,
+                 void* value,
+                 size_t charge,
+                 void (*deleter)(const leveldb::Slice& key,
+                                 void* value)) override {
+    return nullptr;
+  }
+  Handle* Lookup(const leveldb::Slice& key) override { return nullptr; }
+  void Release(Handle* handle) override {}
+  void* Value(Handle* handle) override { return nullptr; }
+  void Erase(const leveldb::Slice& key) override {}
+  uint64_t NewId() override { return 0; }
+  void Prune() override {}
+  size_t TotalCharge() const override { return 0u; }
+};
+
 }  // namespace
 
 // Returns a separate (from the default) block cache for use by web APIs.
@@ -302,9 +328,8 @@
 }
 
 Cache* GetSharedInMemoryBlockCache() {
-  // Zero size cache to prevent cache hits.
-  static leveldb::Cache* s_empty_cache = leveldb::NewLRUCache(0);
-  return s_empty_cache;
+  static base::NoDestructor<NoOpBlockCache> s_empty_cache;
+  return s_empty_cache.get();
 }
 
 bool IsMemEnv(const leveldb::Env* env) {
diff --git a/third_party/libvpx/README.chromium b/third_party/libvpx/README.chromium
index a3dc434..b30ee9e 100644
--- a/third_party/libvpx/README.chromium
+++ b/third_party/libvpx/README.chromium
@@ -1,7 +1,7 @@
 Name: libvpx
 URL: https://chromium.googlesource.com/webm/libvpx
 Version: N/A
-Revision: 6445da1b40da7967ccaee23d1ac46d5c3981b89c
+Revision: 8762f5efb2917765316a198e6713f0bc93b07c9b
 CPEPrefix: cpe:/a:webmproject:libvpx:1.14.0
 License: BSD
 License File: source/libvpx/LICENSE
diff --git a/third_party/libvpx/source/config/vpx_version.h b/third_party/libvpx/source/config/vpx_version.h
index 2cd768c..8eae539f 100644
--- a/third_party/libvpx/source/config/vpx_version.h
+++ b/third_party/libvpx/source/config/vpx_version.h
@@ -2,8 +2,8 @@
 #define VERSION_MAJOR 1
 #define VERSION_MINOR 14
 #define VERSION_PATCH 0
-#define VERSION_EXTRA "236-g6445da1b4"
+#define VERSION_EXTRA "238-g8762f5efb"
 #define VERSION_PACKED \
   ((VERSION_MAJOR << 16) | (VERSION_MINOR << 8) | (VERSION_PATCH))
-#define VERSION_STRING_NOSP "v1.14.0-236-g6445da1b4"
-#define VERSION_STRING " v1.14.0-236-g6445da1b4"
+#define VERSION_STRING_NOSP "v1.14.0-238-g8762f5efb"
+#define VERSION_STRING " v1.14.0-238-g8762f5efb"
diff --git a/third_party/libvpx/source/libvpx b/third_party/libvpx/source/libvpx
index 6445da1..8762f5e 160000
--- a/third_party/libvpx/source/libvpx
+++ b/third_party/libvpx/source/libvpx
@@ -1 +1 @@
-Subproject commit 6445da1b40da7967ccaee23d1ac46d5c3981b89c
+Subproject commit 8762f5efb2917765316a198e6713f0bc93b07c9b
diff --git a/third_party/lit/v3_0/BUILD.gn b/third_party/lit/v3_0/BUILD.gn
index 7357bd8a..2b160ac7 100644
--- a/third_party/lit/v3_0/BUILD.gn
+++ b/third_party/lit/v3_0/BUILD.gn
@@ -18,6 +18,7 @@
     # - tests that need to refer to CrLitElement.
     # - a few chrome/browser/resources/ folders  that hold small UIs.
     # Update when the migration enters its next phase.
+    "//chrome/browser/resources/welcome:build_ts",
     "//chrome/browser/resources/whats_new:build_ts",
     "//chrome/test/data/webui/cr_elements:build_ts",
     "//chrome/test/data/webui/extensions:build_ts",
diff --git a/third_party/nearby/BUILD.gn b/third_party/nearby/BUILD.gn
index cd9edef..5df54c4 100644
--- a/third_party/nearby/BUILD.gn
+++ b/third_party/nearby/BUILD.gn
@@ -292,6 +292,8 @@
     "src/connections/implementation/mediums/bluetooth_classic.cc",
     "src/connections/implementation/mediums/bluetooth_radio.cc",
     "src/connections/implementation/mediums/mediums.cc",
+    "src/connections/implementation/mediums/multiplex/multiplex_frames.cc",
+    "src/connections/implementation/mediums/multiplex/multiplex_output_stream.cc",
     "src/connections/implementation/mediums/webrtc.cc",
     "src/connections/implementation/mediums/wifi_direct.cc",
     "src/connections/implementation/mediums/wifi_hotspot.cc",
@@ -313,6 +315,8 @@
     "src/connections/implementation/mediums/bluetooth_radio.h",
     "src/connections/implementation/mediums/lost_entity_tracker.h",
     "src/connections/implementation/mediums/mediums.h",
+    "src/connections/implementation/mediums/multiplex/multiplex_frames.h",
+    "src/connections/implementation/mediums/multiplex/multiplex_output_stream.h",
     "src/connections/implementation/mediums/webrtc.h",
     "src/connections/implementation/mediums/wifi_direct.h",
     "src/connections/implementation/mediums/wifi_hotspot.h",
@@ -323,6 +327,7 @@
     ":connections_implementation_flags",
     ":connections_implementation_mediums_webrtc",
     ":connections_types",
+    ":multiplex_frames_proto",
     ":platform_base",
     ":platform_base_cancellation_flag",
     ":platform_base_util",
@@ -337,6 +342,7 @@
   configs -= [ "//build/config/compiler:chromium_code" ]
   configs += [ "//build/config/compiler:no_chromium_code" ]
 }
+
 source_set("connections_implementation_mediums_utils") {
   public_configs = [
     ":nearby_include_config",
@@ -361,6 +367,7 @@
   configs -= [ "//build/config/compiler:chromium_code" ]
   configs += [ "//build/config/compiler:no_chromium_code" ]
 }
+
 source_set("connections_implementation_mediums_webrtc_data_types") {
   public_configs = [
     ":nearby_include_config",
@@ -379,6 +386,7 @@
   configs -= [ "//build/config/compiler:chromium_code" ]
   configs += [ "//build/config/compiler:no_chromium_code" ]
 }
+
 source_set("connections_implementation_mediums_webrtc") {
   public_configs = [
     ":nearby_include_config",
@@ -1240,6 +1248,12 @@
   proto_out_dir = "third_party/nearby"
 }
 
+proto_library("multiplex_frames_proto") {
+  proto_in_dir = "src"
+  sources = [ "${proto_in_dir}/proto/mediums/multiplex_frames.proto" ]
+  proto_out_dir = "third_party/nearby"
+}
+
 proto_library("web_rtc_signaling_frames_proto") {
   proto_in_dir = "src"
   sources = [ "${proto_in_dir}/proto/mediums/web_rtc_signaling_frames.proto" ]
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index a891cfa..5a9d3a1 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby
-Version: f788b891908dd93274918e5b919e09136f3f1efd
+Version: 78505e91f7e054444030aec55110893b4fc62500
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/nearby/src b/third_party/nearby/src
index f788b891..78505e9 160000
--- a/third_party/nearby/src
+++ b/third_party/nearby/src
@@ -1 +1 @@
-Subproject commit f788b891908dd93274918e5b919e09136f3f1efd
+Subproject commit 78505e91f7e054444030aec55110893b4fc62500
diff --git a/third_party/openscreen/src b/third_party/openscreen/src
index d8a6ddb..624f91b 160000
--- a/third_party/openscreen/src
+++ b/third_party/openscreen/src
@@ -1 +1 @@
-Subproject commit d8a6ddbcc09d44d27c9c0aa114e5d63525872089
+Subproject commit 624f91b189a5a6fd35b1d3c43c60678c1f111dff
diff --git a/third_party/pdfium b/third_party/pdfium
index 0e4ff8c..f0dc864 160000
--- a/third_party/pdfium
+++ b/third_party/pdfium
@@ -1 +1 @@
-Subproject commit 0e4ff8cd885348e2ac01ea640f666634e7a852a7
+Subproject commit f0dc864fb19ec85c9496387f1e806b14f5e6d999
diff --git a/third_party/perfetto b/third_party/perfetto
index ffad8c9..b1676f9 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit ffad8c926639b3ee7204de0743cfd52b40d9b868
+Subproject commit b1676f92d878b5bed2dbe493a9cbb9125a9ec2d5
diff --git a/third_party/skia b/third_party/skia
index df6d08a..28579a8 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit df6d08abb447b780c4075cd56f87582b829d2948
+Subproject commit 28579a88aa9c12ef23c1d3ab798aba40f85ea0d8
diff --git a/third_party/webgpu-cts/src b/third_party/webgpu-cts/src
index 95e42cb..acb4750 160000
--- a/third_party/webgpu-cts/src
+++ b/third_party/webgpu-cts/src
@@ -1 +1 @@
-Subproject commit 95e42cb72bc64eaae08344dfc994aba19ec1b59f
+Subproject commit acb475026d7ac90725c629ef3664267c7aed72a1
diff --git a/third_party/webgpu-cts/ts_sources.txt b/third_party/webgpu-cts/ts_sources.txt
index 44df94a9..ed6e32cd 100644
--- a/third_party/webgpu-cts/ts_sources.txt
+++ b/third_party/webgpu-cts/ts_sources.txt
@@ -756,6 +756,7 @@
 src/webgpu/shader/validation/expression/call/builtin/dot4U8Packed.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts
+src/webgpu/shader/validation/expression/call/builtin/extractBits.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/faceForward.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/firstLeadingBit.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/firstTrailingBit.spec.ts
@@ -791,6 +792,7 @@
 src/webgpu/shader/validation/expression/call/builtin/sqrt.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/step.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts
+src/webgpu/shader/validation/expression/call/builtin/tanh.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/textureGather.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/textureGatherCompare.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/textureLoad.spec.ts
diff --git a/third_party/webrtc b/third_party/webrtc
index ae53490..0bb59b4 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit ae53490d184c03116416a9c1025c40a34d37c3af
+Subproject commit 0bb59b4edd23d4e69de14b316312571146b6448d
diff --git a/tools/binary_size/libsupersize/apkanalyzer.py b/tools/binary_size/libsupersize/apkanalyzer.py
index ffbcfe3..905057e 100644
--- a/tools/binary_size/libsupersize/apkanalyzer.py
+++ b/tools/binary_size/libsupersize/apkanalyzer.py
@@ -26,7 +26,6 @@
 import string_extract
 
 _TOTAL_NODE_NAME = '<TOTAL>'
-_OUTLINED_PREFIX = '$$Outlined$'
 
 # A limit on the number of symbols a DEX string literal can have, before these
 # symbols are compacted into shared symbols. Increasing this value causes more
@@ -47,6 +46,14 @@
 # at the cost leaving ~5100 byte in binary sizes unresolved into aliases).
 _DEX_STRING_MAX_SAME_NAME_ALIAS_COUNT = 6
 
+# Synthetics that map 1:1 with the class they are a suffix on.
+_CLASS_SPECIFIC_SYNTHETICS = (
+    'ExternalSyntheticLambda',
+    'ExternalSyntheticApiModelOutline',
+    'ExternalSyntheticServiceLoad',
+    'Lambda',
+)
+
 
 def _ParseJarInfoFile(file_name):
   with open(file_name, 'r') as info:
@@ -183,127 +190,74 @@
   return value
 
 
-# Visible for testing.
-class LambdaNormalizer:
-  def __init__(self):
-    self._lambda_by_class_counter = collections.defaultdict(int)
-    self._lambda_name_to_nested_number = {}
+def _NormalizeName(orig_name):
+  """Extracts outer class name and normalizes names with hashes in them.
 
-  def _GetLambdaName(self, class_path, base_name, prefix=''):
-    lambda_number = self._lambda_name_to_nested_number.get(class_path)
-    if lambda_number is None:
-      # First time we've seen this lambda, increment nested class count.
-      lambda_number = self._lambda_by_class_counter[base_name]
-      self._lambda_name_to_nested_number[class_path] = lambda_number
-      self._lambda_by_class_counter[base_name] = lambda_number + 1
-    return prefix + base_name + '$$Lambda$' + str(lambda_number)
+  Returns:
+    outer_class: The outer class. Example: package.Class
+      Returns None for classes that are outlines.
+    new_name: Normalized name.
+  """
+  # May be reassigned by one of the cases below.
+  outer_class = _TruncateFrom(orig_name, '$')
+
+  # $$ is the convention for a synthetic class and all known desugared lambda
+  synthetic_marker_idx = orig_name.find('$$')
+  if synthetic_marker_idx == -1:
+    return outer_class, orig_name
+
+  synthetic_part = orig_name[synthetic_marker_idx + 2:]
+
+  # Example: package.Cls$$InternalSyntheticLambda$0$81073ff6$0
+  if synthetic_part.startswith('InternalSyntheticLambda$'):
+    next_dollar_idx = orig_name.index('$',
+        synthetic_marker_idx + len('$$InternalSyntheticLambda$'))
+    return outer_class, orig_name[:next_dollar_idx]
+
+  # Ensure we notice if a new type of InternalSythetic pops up.
+  # E.g. to see if it follows the same naming scheme.
+  assert not synthetic_part.startswith('Internal'), f'Unrecognized: {orig_name}'
+
+  if synthetic_part.startswith(_CLASS_SPECIFIC_SYNTHETICS):
+    return outer_class, orig_name
+
+  return None, orig_name
 
 
-  def ExtractOuterClassAndDesugarLambda(self, class_path):
-    """Extracts outer class name and converts Lambda to Desugar format.
-
-    Args:
-      class_path: Class path as d8 desugared name, possibly including inner
-      classes and Lambda parts. Example:
-      package.Class$$Lambda$2$$InternalSyntheticOutline$8$cbe941dd782$0
-
-    Returns:
-      outer_class: The outer class. Example: package.Class
-      new_name: |class_path| converted to Desugar format, or None if it is not a
-        Lambda. Example: package.Class$$Lambda$0
-    """
-    # May be reassigned by one of the cases below.
-    outer_class = _TruncateFrom(class_path, '$')
-
-    # $$ is the convention for a synthetic class and all known desugared lambda
-    # classes have 'Lambda' in the synthetic part of its name. If it doesn't
-    # then it's almost certainly not a desugared lambda class.
-    if 'Lambda' not in class_path[class_path.find('$$'):]:
-      return outer_class, None
-
-    # Example: package.AnimatedProgressBar$$InternalSyntheticLambda$3$81073ff6$0
-    # Example: package.Class$$Lambda$2$$InternalSyntheticOutline$8$cbe941dd782$0
-    match = re.fullmatch(
-        # The base_name group needs to be non-greedy/minimal (using +?) since we
-        # want it to not include $$Lambda$28 when present.
-        r'(?P<base_name>.+?)(\$\$Lambda\$\d+)?'
-        r'\$\$InternalSynthetic[a-zA-Z0-9_]+'
-        r'\$\d+\$[0-9a-f]+\$\d+',
-        class_path)
-    if match:
-      new_name = self._GetLambdaName(class_path=class_path,
-                                     base_name=match.group('base_name'))
-      return outer_class, new_name
-    # Example: AnimatedProgressBar$$ExternalSyntheticLambda0
-    # Example: AutofillAssistant$$Lambda$2$$ExternalSyntheticOutline0
-    # Example: ContextMenuCoord$$Lambda$2$$ExternalSyntheticThrowCCEIfNotNull0
-    match = re.fullmatch(
-        r'(?P<base_name>.+?)(\$\$Lambda\$\d+)?'
-        r'\$\$ExternalSynthetic[a-zA-Z0-9_]+', class_path)
-    if match:
-      new_name = self._GetLambdaName(class_path=class_path,
-                                     base_name=match.group('base_name'),
-                                     prefix=_OUTLINED_PREFIX)
-      return outer_class, new_name
-    # Example: package.FirebaseInstallationsRegistrar$$Lambda$1
-    match = re.fullmatch(r'(?P<base_name>.+)\$\$Lambda\$\d+', class_path)
-    if match:
-      # Although these are already valid names, re-number them to avoid name
-      # collisions with renamed InternalSyntheticLambdas.
-      new_name = self._GetLambdaName(class_path=class_path,
-                                     base_name=match.group('base_name'))
-      return outer_class, new_name
-    # Example: org.-$$Lambda$StackAnimation$Nested1$kjevdDQ8V2zqCrdieLqWLHzk
-    # Assume that the last portion of the name after $ is the hash identifier.
-    match = re.fullmatch(
-        r'(?P<package>.+)-\$\$Lambda\$(?P<class>[^$]+)(?P<nested>.*)\$[^$]+',
-        class_path)
-    if match:
-      package_name = match.group('package')
-      class_name = match.group('class')
-      nested_classes = match.group('nested')
-      base_name = package_name + class_name + nested_classes
-      new_name = self._GetLambdaName(class_path=class_path, base_name=base_name)
-      outer_class = package_name + class_name
-      return outer_class, new_name
-    assert False, (
-        'No valid match for new lambda name format: ' + class_path + '\n'
-        'Please update https://crbug.com/1208385 with this error so we can '
-        'update the lambda normalization code.')
-    return None
-
-  def Normalize(self, class_path, full_name):
-    """Make d8 desugared lambdas look the same as Desugar ones."""
-    # Desugar lambda: org.Promise$Nested1$$Lambda$0
-    # 1) Need to prefix with proper class name so that they will show as nested.
-    # 2) Need to suffix with number so that they diff better.
-    # Original name will be kept as "object_path".
-    # See tests for a more comprehensive list of what d8 currently generates.
-    outer_class, new_name = self.ExtractOuterClassAndDesugarLambda(class_path)
-    if new_name:
-      full_name = full_name.replace(class_path, new_name)
-    return outer_class, full_name
+def NormalizeLine(orig_name, full_name):
+  """Normalizes a line from apkanalyzer output.
+  Args:
+    orig_name: The original name from apkanalyzer output.
+    full_name: The full name of the symbol.
+  Returns:
+    outer_class: The outer class. Example: package.Class
+      Returns None for classes that are outlines.
+    new_full_name: Normalized full name.
+  """
+  # See tests for a more comprehensive list of what d8 currently generates.
+  outer_class, new_name = _NormalizeName(orig_name)
+  if new_name is not orig_name:
+    full_name = full_name.replace(orig_name, new_name)
+  return outer_class, full_name
 
 
 def _MakeDexObjectPath(package_name, is_outlined):
   if is_outlined:
     # Create a special meta-directory for outlined lambdas to easily monitor
     # their total size and spot regressions.
-    return posixpath.join(models.APK_PREFIX_PATH, 'Outlined',
-                          *package_name.split('.'))
+    return posixpath.join(models.OUTLINES_PREFIX_PATH, *package_name.split('.'))
   return posixpath.join(models.APK_PREFIX_PATH, *package_name.split('.'))
 
 
 # Visible for testing.
-def CreateDexSymbol(name, size, source_map, lambda_normalizer):
+def CreateDexSymbol(name, size, source_map):
   parts = name.split(' ')  # (class_name, return_type, method_name)
   new_package = parts[0]
 
   if new_package == _TOTAL_NODE_NAME:
     return None
 
-  # Make d8 desugared lambdas look the same as Desugar ones.
-  outer_class, name = lambda_normalizer.Normalize(new_package, name)
+  outer_class, name = NormalizeLine(new_package, name)
 
   # Look for class merging.
   old_package = new_package
@@ -315,16 +269,28 @@
     last_idx = method.rfind('.', 0, last_idx)
     if last_idx != -1:
       old_package = method[:last_idx]
-      outer_class, name = lambda_normalizer.Normalize(old_package, name)
 
-  source_path = source_map.get(outer_class, '')
-  object_path = _MakeDexObjectPath(old_package,
-                                   name.startswith(_OUTLINED_PREFIX))
+      # TODO(b/333617478): Delete this work-around when R8 mapping files no
+      #     longer output this pattern.
+      suspect_class_name = old_package
+      if suspect_class_name.startswith('WV.'):
+        suspect_class_name = suspect_class_name[3:]
+      if ('.' not in suspect_class_name
+          and new_package.endswith(f'.{suspect_class_name}')):
+        name = name.replace(f' {old_package}.', ' ')
+        old_package = new_package
+      else:
+        # Non-workaround case:
+        outer_class, name = NormalizeLine(old_package, name)
+
+  is_outlined = outer_class == None
+  object_path = _MakeDexObjectPath(old_package, is_outlined)
   if name.endswith(')'):
     section_name = models.SECTION_DEX_METHOD
   else:
     section_name = models.SECTION_DEX
 
+  source_path = source_map.get(outer_class, '')
   return models.Symbol(section_name,
                        size,
                        full_name=name,
@@ -335,9 +301,8 @@
 def _SymbolsFromNodes(nodes, source_map):
   # Use (DEX_METHODS, DEX) buckets to speed up sorting.
   symbol_buckets = ([], [])
-  lambda_normalizer = LambdaNormalizer()
   for _, name, node_size in nodes:
-    symbol = CreateDexSymbol(name, node_size, source_map, lambda_normalizer)
+    symbol = CreateDexSymbol(name, node_size, source_map)
     if symbol:
       bucket_index = int(symbol.section_name is models.SECTION_DEX)
       symbol_buckets[bucket_index].append(symbol)
@@ -435,7 +400,6 @@
   # Code strings: Strings accessed via class -> method -> code -> string.
   # These are extracted into separate symbols ,aliases among referring classes.
   fresh_string_idx_set = set(range(len(dexfile.string_data_item_list)))
-  lambda_normalizer = LambdaNormalizer()
   object_path = str(apk_path)
   dex_string_symbols = []
   string_iter = _GenDexStringsUsedByClasses(dexfile, class_deobfuscation_map)
@@ -444,8 +408,7 @@
     num_aliases = len(string_user_class_names)
     aliases = []
     for class_name in string_user_class_names:
-      outer_class, _ = lambda_normalizer.ExtractOuterClassAndDesugarLambda(
-          class_name)
+      outer_class, class_name = _NormalizeName(class_name)
       full_name = string_extract.GetNameOfStringLiteralBytes(
           decoded_string.encode('utf-8', errors='surrogatepass'))
       source_path = (source_map.get(outer_class, '')
diff --git a/tools/binary_size/libsupersize/apkanalyzer_test.py b/tools/binary_size/libsupersize/apkanalyzer_test.py
index e3798fea..5defe92ce 100755
--- a/tools/binary_size/libsupersize/apkanalyzer_test.py
+++ b/tools/binary_size/libsupersize/apkanalyzer_test.py
@@ -94,133 +94,79 @@
     ], nodes)
 
   def assertNormalizedTo(self, class_path, expected_outer_class, expected_name):
-    lambda_normalizer = apkanalyzer.LambdaNormalizer()
-    actual_outer_class, actual_name = lambda_normalizer.Normalize(
+    actual_outer_class, actual_name = apkanalyzer.NormalizeLine(
         class_path, class_path)
     self.assertEqual(expected_outer_class, actual_outer_class)
     self.assertEqual(expected_name, actual_name)
 
-  def testLambdaNormalizer_normalLambda(self):
+  def testNoramlize_internalSyntheticLambda(self):
     self.assertNormalizedTo(
-        class_path='package.FirebaseInstallationsRegistrar$$Lambda$1',
-        expected_outer_class='package.FirebaseInstallationsRegistrar',
-        # Always re-number lambdas.
-        expected_name='package.FirebaseInstallationsRegistrar$$Lambda$0',
+        class_path='pkg.Cls$$InternalSyntheticLambda$3$81073ff626$0',
+        expected_outer_class='pkg.Cls',
+        expected_name='pkg.Cls$$InternalSyntheticLambda$3')
+
+  def testNoramlize_externalSyntheticLambda(self):
+    self.assertNormalizedTo(
+        class_path='pkg.AnimatedProgressBar$$ExternalSyntheticLambda0',
+        expected_outer_class='pkg.AnimatedProgressBar',
+        expected_name=('pkg.AnimatedProgressBar$$ExternalSyntheticLambda0'))
+
+  # Google3 still uses this format.
+  def testNoramlize_DesugarLambda(self):
+    self.assertNormalizedTo(class_path='pkg.Cls$$Lambda$1',
+                            expected_outer_class='pkg.Cls',
+                            expected_name='pkg.Cls$$Lambda$1')
+
+  def testNoramlize_apiModelOutline(self):
+    self.assertNormalizedTo(
+        class_path='pkg.Cls$$ExternalSyntheticApiModelOutline0',
+        expected_outer_class='pkg.Cls',
+        expected_name='pkg.Cls$$ExternalSyntheticApiModelOutline0')
+
+  def testNoramlize_r8Outline(self):
+    self.assertNormalizedTo(class_path='pkg.Cls$$ExternalSyntheticOutline0',
+                            expected_outer_class=None,
+                            expected_name='pkg.Cls$$ExternalSyntheticOutline0')
+
+  def testNoramlize_externalSyntheticCodegen(self):
+    self.assertNormalizedTo(
+        class_path='pkg.Cls$$ExternalSyntheticThrowCCEIfNotNull0',
+        expected_outer_class=None,
+        expected_name=('pkg.Cls$$ExternalSyntheticThrowCCEIfNotNull0'))
+
+    self.assertNormalizedTo(
+        class_path='pkg.Cls$$ExternalSyntheticBackportWithForwarding0',
+        expected_outer_class=None,
+        expected_name=('pkg.Cls$$ExternalSyntheticBackportWithForwarding0'))
+
+  def testNoramlize_externalSyntheticOther(self):
+    self.assertNormalizedTo(
+        class_path='pkg.Cls$$ExternalSyntheticServiceLoad0',
+        expected_outer_class='pkg.Cls',
+        expected_name='pkg.Cls$$ExternalSyntheticServiceLoad0',
     )
 
-  def testLambdaNormalizer_externalSyntheticLambda(self):
-    self.assertNormalizedTo(
-        class_path='AnimatedProgressBar$$ExternalSyntheticLambda0',
-        expected_outer_class='AnimatedProgressBar',
-        expected_name=('$$Outlined$AnimatedProgressBar$$Lambda$0'),
-    )
-
-  def testLambdaNormalizer_externalSyntheticOutlineLambda(self):
-    self.assertNormalizedTo(
-        class_path='AutofillAssistant$$Lambda$2$$ExternalSyntheticOutline0',
-        expected_outer_class='AutofillAssistant',
-        expected_name=('$$Outlined$AutofillAssistant$$Lambda$0'),
-    )
-
-  def testLambdaNormalizer_externalSyntheticLambdaWithExtraText(self):
-    self.assertNormalizedTo(
-        # pylint: disable=line-too-long
-        class_path='ContextMenuCoordinator$$Lambda$1$$ExternalSyntheticThrowCCEIfNotNull0',
-        expected_outer_class='ContextMenuCoordinator',
-        expected_name=('$$Outlined$ContextMenuCoordinator$$Lambda$0'),
-    )
-
-  def testLambdaNormalizer_internalSyntheticLambda(self):
-    self.assertNormalizedTo(
-        class_path=
-        # pylint: disable=line-too-long
-        'package.AnimatedProgressBar$$InternalSyntheticLambda$3$81073ff626580374a45b03cb5c962b81fe2a950064649dc98bf006e85619e92f$0',
-        expected_outer_class='package.AnimatedProgressBar',
-        expected_name='package.AnimatedProgressBar$$Lambda$0',
-    )
-
-  def testLambdaNormalizer_internalSyntheticOutlineLambda(self):
-    self.assertNormalizedTo(
-        class_path=
-        # pylint: disable=line-too-long
-        'package.ChromeActivity$$Lambda$28$$InternalSyntheticOutline$807$cbe941dd7bdbd82d4d93f21de2ea4e38c4468513f97dc90dcb54501b2fc335a6$0',
-        expected_outer_class='package.ChromeActivity',
-        expected_name='package.ChromeActivity$$Lambda$0',
-    )
-
-  def testLambdaNormalizer_oldLambdaFormat(self):
-    self.assertNormalizedTo(
-        class_path=
-        'org.-$$Lambda$StackAnimation$Nested1$kjevdDQ8V2zqCrdieLqWLHzk',
-        expected_outer_class='org.StackAnimation',
-        expected_name='org.StackAnimation$Nested1$$Lambda$0',
-    )
-
-  def testLambdaNormalizer_oldLambdaFormatWithDexSuffix(self):
-    self.assertNormalizedTo(
-        class_path=
-        'org.-$$Lambda$StackAnimation$Nested1$kjevdDQ8V2zqCrdieLqWLHzk.dex',
-        expected_outer_class='org.StackAnimation',
-        expected_name='org.StackAnimation$Nested1$$Lambda$0',
-    )
-
-  def testLambdaNormalizer_classNameIncludesLambda(self):
-    self.assertNormalizedTo(
-        class_path='package.DisposableLambdaObserver',
-        expected_outer_class='package.DisposableLambdaObserver',
-        expected_name='package.DisposableLambdaObserver',
-    )
-
-  def testLambdaNormalizer_prefix(self):
-    lambda_normalizer = apkanalyzer.LambdaNormalizer()
-    name = 'org.-$$Lambda$StackAnimation$Nested1$kjevdeLqWLHzk foo bar'
-    package = name.split(' ')[0]
-    expected_outer_class = 'org.StackAnimation'
-    expected_name = 'org.StackAnimation$Nested1$$Lambda$0 foo bar'
-    self.assertEqual((expected_outer_class, expected_name),
-                     lambda_normalizer.Normalize(package, name))
-
-  def testLambdaNormalizer_lambdaCounting(self):
-    lambda_normalizer = apkanalyzer.LambdaNormalizer()
-    name = 'org.-$$Lambda$StackAnimation$Nested1$kjevdDQ8V2zqCrdieLqWLHzk'
-    expected_outer_class = 'org.StackAnimation'
-    expected_name = 'org.StackAnimation$Nested1$$Lambda$0'
-    # Ensure multiple calls to the same class maps to same number.
-    self.assertEqual((expected_outer_class, expected_name),
-                     lambda_normalizer.Normalize(name, name))
-    self.assertEqual((expected_outer_class, expected_name),
-                     lambda_normalizer.Normalize(name, name))
-    name = 'org.-$$Lambda$StackAnimation$Nested1$kjevdDQ8V2zqCrdieLqWLHzk2'
-    expected_name = 'org.StackAnimation$Nested1$$Lambda$1'
-    self.assertEqual((expected_outer_class, expected_name),
-                     lambda_normalizer.Normalize(name, name))
-
-  def testLambdaNormalizer_multiSameLine(self):
-    lambda_normalizer = apkanalyzer.LambdaNormalizer()
-    name = ('org.-$$Lambda$StackAnimation$Nested1$kevdDQ8V2zqCrdieLqWLHzk '
-            'org.-$$Lambda$Other$kjevdDQ8V2zqCrdieLqWLHzk bar')
-    package = name.split(' ')[0]
-    expected_outer_class = 'org.StackAnimation'
-    expected_name = ('org.StackAnimation$Nested1$$Lambda$0 '
-                     'org.-$$Lambda$Other$kjevdDQ8V2zqCrdieLqWLHzk bar')
-    self.assertEqual((expected_outer_class, expected_name),
-                     lambda_normalizer.Normalize(package, name))
+  def testNoramlize_multiSameLine(self):
+    name = ('pkg1.Cls$$InternalSyntheticLambda$3$81073ff626$0 '
+            'pkg2.Cls$$InternalSyntheticLambda$0$81073ff626$0 bar')
+    outer_class = name.split(' ')[0]
+    expected_name = ('pkg1.Cls$$InternalSyntheticLambda$3 '
+                     'pkg2.Cls$$InternalSyntheticLambda$0$81073ff626$0 bar')
+    self.assertEqual(('pkg1.Cls', expected_name),
+                     apkanalyzer.NormalizeLine(outer_class, name))
     name = expected_name
-    package = name.split(' ')[1]
-    expected_outer_class = 'org.Other'
-    expected_name = ('org.StackAnimation$Nested1$$Lambda$0 '
-                     'org.Other$$Lambda$0 bar')
-    self.assertEqual((expected_outer_class, expected_name),
-                     lambda_normalizer.Normalize(package, name))
+    outer_class = name.split(' ')[1]
+    expected_name = ('pkg1.Cls$$InternalSyntheticLambda$3 '
+                     'pkg2.Cls$$InternalSyntheticLambda$0 bar')
+    self.assertEqual(('pkg2.Cls', expected_name),
+                     apkanalyzer.NormalizeLine(outer_class, name))
 
   def testCreateDexSymbol_normal(self):
     name = ('org.StackAnimation org.ChromeAnimation '
             'createReachTopAnimatorSet(org.StackTab[],float)')
     size = 1
     source_map = {}
-    lambda_normalizer = apkanalyzer.LambdaNormalizer()
-    symbol = apkanalyzer.CreateDexSymbol(name, size, source_map,
-                                         lambda_normalizer)
+    symbol = apkanalyzer.CreateDexSymbol(name, size, source_map)
     self.assertEqual('$APK/org/StackAnimation', symbol.object_path)
 
   def testCreateDexSymbol_classMerged_noSource(self):
@@ -228,9 +174,7 @@
             'org.OldClass.createReachTopAnimatorSet(org.StackTab[],float)')
     size = 1
     source_map = {}
-    lambda_normalizer = apkanalyzer.LambdaNormalizer()
-    symbol = apkanalyzer.CreateDexSymbol(name, size, source_map,
-                                         lambda_normalizer)
+    symbol = apkanalyzer.CreateDexSymbol(name, size, source_map)
     self.assertEqual('$APK/org/OldClass', symbol.object_path)
 
   def testCreateDexSymbol_classMerged_withSource(self):
@@ -238,9 +182,7 @@
             'org.OldClass.createReachTopAnimatorSet(org.StackTab[],float)')
     size = 1
     source_map = {'org.OldClass': 'old_path.java'}
-    lambda_normalizer = apkanalyzer.LambdaNormalizer()
-    symbol = apkanalyzer.CreateDexSymbol(name, size, source_map,
-                                         lambda_normalizer)
+    symbol = apkanalyzer.CreateDexSymbol(name, size, source_map)
     self.assertEqual('$APK/org/OldClass', symbol.object_path)
     self.assertEqual('old_path.java', symbol.source_path)
 
@@ -248,18 +190,14 @@
     name = 'org.NewClass int org.OldClass.createReachTopAnimatorSet'
     size = 1
     source_map = {}
-    lambda_normalizer = apkanalyzer.LambdaNormalizer()
-    symbol = apkanalyzer.CreateDexSymbol(name, size, source_map,
-                                         lambda_normalizer)
+    symbol = apkanalyzer.CreateDexSymbol(name, size, source_map)
     self.assertEqual('$APK/org/OldClass', symbol.object_path)
 
   def testCreateDexSymbol_total(self):
     name = '<TOTAL>'
     size = 1
     source_map = {}
-    lambda_normalizer = apkanalyzer.LambdaNormalizer()
-    symbol = apkanalyzer.CreateDexSymbol(name, size, source_map,
-                                         lambda_normalizer)
+    symbol = apkanalyzer.CreateDexSymbol(name, size, source_map)
     self.assertIsNone(symbol)
 
 
diff --git a/tools/binary_size/libsupersize/models.py b/tools/binary_size/libsupersize/models.py
index d92cc9f3..98e13004 100644
--- a/tools/binary_size/libsupersize/models.py
+++ b/tools/binary_size/libsupersize/models.py
@@ -59,6 +59,7 @@
 SECTION_MULTIPLE = '.*'
 
 APK_PREFIX_PATH = '$APK'
+OUTLINES_PREFIX_PATH = '$APK/$OUTLINES'
 NATIVE_PREFIX_PATH = '$NATIVE'
 SYSTEM_PREFIX_PATH = '$SYSTEM'
 
diff --git a/tools/chromeos/gen_deps.sh b/tools/chromeos/gen_deps.sh
index fe21a69d..1e5147e 100755
--- a/tools/chromeos/gen_deps.sh
+++ b/tools/chromeos/gen_deps.sh
@@ -57,7 +57,7 @@
 readonly TARGET=${1}
 if ! [ -d $TARGET ]; then
   if ! [ -f $TARGET ]; then
-    echo "${TARGET} does not exit."
+    echo "${TARGET} does not exist."
   else
     echo "${TARGET} is not a directory."
   fi
@@ -65,15 +65,15 @@
 fi
 
 # Directories whose files should be listed separately.
-readonly NO_DIR=("chrome" "chrome/browser" "chrome/common"
-                 "chrome/browser/extensions" "chrome/browser/ui"
-                 "chrome/browser/ui/webui" "chrome/browser/ui/views"
-                 "chrome/browser/web_applications")
+readonly NO_DIR=("chrome" "chrome/browser" "chrome/browser/extensions"
+                 "chrome/browser/ui" "chrome/browser/ui/views"
+                 "chrome/browser/ui/webui" "chrome/browser/web_applications"
+                 "chrome/common")
 
 # Check above directories exist.
 for d in ${NO_DIR[@]}; do
   if [ ! -d $d ]; then
-    echo "Warning: Directory $d does not exit. " \
+    echo "Warning: Directory $d does not exist. " \
       "Please update the directory list NO_DIR in $(basename $0)."
   fi
 done
@@ -106,12 +106,12 @@
   fi
 done
 
-# Print dirs and files in DEPS format.
-for d in "${dirs[@]}"; do
-  echo "  \"+$d\","
-done
+# Combine arrays and sort alphabetically.
+files_and_dirs=( "${dirs[@]}" "${files[@]}" )
+sorted=($(printf '%s\n' "${files_and_dirs[@]}" | sort))
 
-for d in "${files[@]}"; do
-  echo "  \"+$d\","
+# Print in DEPS format.
+for i in "${sorted[@]}"; do
+  echo "  \"+$i\","
 done
 
diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index 93b5287..00f50e9 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -935,6 +935,32 @@
     cflags += zstd_cflags
     cxxflags += zstd_cflags
 
+  lit_excludes = []
+  if sys.platform.startswith('linux'):
+    lit_excludes += [
+        # See SANITIZER_OVERRIDE_INTERCEPTORS above: We disable crypt_r()
+        # interception, so its tests can't pass.
+        '^SanitizerCommon-(a|l|m|ub|t)san-x86_64-Linux :: Linux/crypt_r.cpp$',
+        # fstat and sunrpc tests fail due to sysroot/host mismatches
+        # (crbug.com/1459187).
+        '^MemorySanitizer-.* f?stat(at)?(64)?.cpp$',
+        '^.*Sanitizer-.*sunrpc.*cpp$',
+        # sysroot/host glibc version mismatch, crbug.com/1506551
+        '^.*Sanitizer.*mallinfo2.cpp$',
+    ]
+  elif sys.platform == 'darwin':
+    lit_excludes += [
+        # Fails on macOS 14, crbug.com/332589870
+        '^.*Sanitizer.*Darwin/malloc_zone.cpp$',
+        # Fails with a recent ld, crbug.com/332589870
+        '^.*ContinuousSyncMode/darwin-proof-of-concept.c$',
+        '^.*instrprof-darwin-exports.c$',
+    ]
+  test_env = None
+  if lit_excludes:
+    test_env = os.environ.copy()
+    test_env['LIT_FILTER_OUT'] = '|'.join(lit_excludes)
+
   if args.bootstrap:
     print('Building bootstrap compiler')
     if os.path.exists(LLVM_BOOTSTRAP_DIR):
@@ -991,7 +1017,7 @@
                setenv=True)
     RunCommand(['ninja'] + goma_ninja_args, setenv=True)
     if args.run_tests:
-      RunCommand(['ninja', 'check-all'], setenv=True)
+      RunCommand(['ninja', 'check-all'], env=test_env, setenv=True)
     RunCommand(['ninja', 'install'], setenv=True)
 
     if sys.platform == 'win32':
@@ -1500,33 +1526,8 @@
     RunCommand(['ninja', '-C', LLVM_BUILD_DIR, 'cr-check-all'], setenv=True)
 
   if not args.build_mac_arm and args.run_tests:
-    lit_excludes = []
-    if sys.platform.startswith('linux'):
-      lit_excludes += [
-          # See SANITIZER_OVERRIDE_INTERCEPTORS above: We disable crypt_r()
-          # interception, so its tests can't pass.
-          '^SanitizerCommon-(a|l|m|ub|t)san-x86_64-Linux :: Linux/crypt_r.cpp$',
-          # fstat and sunrpc tests fail due to sysroot/host mismatches
-          # (crbug.com/1459187).
-          '^MemorySanitizer-.* f?stat(at)?(64)?.cpp$',
-          '^.*Sanitizer-.*sunrpc.*cpp$',
-          # sysroot/host glibc version mismatch, crbug.com/1506551
-          '^.*Sanitizer.*mallinfo2.cpp$'
-      ]
-    elif sys.platform == 'darwin':
-      lit_excludes += [
-          # Fails on macOS 14, crbug.com/332589870
-          '^.*Sanitizer.*Darwin/malloc_zone.cpp$',
-          # Fails with a recent ld, crbug.com/332589870
-          '^.*ContinuousSyncMode/darwin-proof-of-concept.c$',
-          '^.*instrprof-darwin-exports.c$',
-      ]
-    env = None
-    if lit_excludes:
-      env = os.environ.copy()
-      env['LIT_FILTER_OUT'] = '|'.join(lit_excludes)
     RunCommand(['ninja', '-C', LLVM_BUILD_DIR, 'check-all'],
-               env=env,
+               env=test_env,
                setenv=True)
   if args.install_dir:
     RunCommand(['ninja', 'install'], setenv=True)
diff --git a/tools/crates/PRESUBMIT.py b/tools/crates/PRESUBMIT.py
new file mode 100644
index 0000000..f1dd4eed
--- /dev/null
+++ b/tools/crates/PRESUBMIT.py
@@ -0,0 +1,22 @@
+# 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.
+"""Runs Python unit tests in this directory.
+"""
+
+PRESUBMIT_VERSION = '2.0.0'
+
+
+def CheckPythonUnittestsPass(input_api, output_api):
+    results = []
+    this_dir = input_api.PresubmitLocalPath()
+
+    results += input_api.RunTests(
+        input_api.canned_checks.GetUnitTestsInDirectory(
+            input_api,
+            output_api,
+            this_dir,
+            files_to_check=['.*unittest.*\.py$'],
+            env=None))
+
+    return results
diff --git a/tools/crates/create_update_cl.md b/tools/crates/create_update_cl.md
index fdb673a8..6b4967b 100644
--- a/tools/crates/create_update_cl.md
+++ b/tools/crates/create_update_cl.md
@@ -33,7 +33,9 @@
 run.
 
 (Side-note: outside the rotation one may also use the script to update a single
-crate - e.g. `tools/crates/create_update_cl.py single <crate name>`.)
+crate - e.g. `tools/crates/create_update_cl.py single bytemuck`.  When working
+with multi-epoch/version crates the old version to update can be specified
+as follows: `tools/crates/create_update_cl.py single syn@2.0.55`.)
 
 Before the auto-generated CLs can be landed, some additional manual steps need
 to be done first - see the sections below.
diff --git a/tools/crates/create_update_cl.py b/tools/crates/create_update_cl.py
index 834ad7c..a1ef1d3 100755
--- a/tools/crates/create_update_cl.py
+++ b/tools/crates/create_update_cl.py
@@ -15,6 +15,7 @@
 import sys
 import textwrap
 import toml
+from dataclasses import dataclass
 from typing import List, Set, Dict
 
 # Throughout the script, the following naming conventions are used (illustrated
@@ -25,11 +26,10 @@
 # * `crate_id`     : "syn@2.0.50" string (syntax used by `cargo`)
 # * `crate_epoch`  : "syn@v2" string (made up syntax)
 #
-# The `old_versions` and `new_versions` dictionaries use `crate_epoch` as a key
-# and `crate_version` as the value.  Note that `crate_name` may not be unique
-# (e.g. if there is both `syn@1.0.109` and `syn@2.0.50`).  Also note that
-# `crate_epoch` doesn't change during a minor version update (such as the one
-# that this script produces in `auto` and `single` modes).
+# Note that `crate_name` may not be unique (e.g. if there is both `syn@1.0.109`
+# and `syn@2.0.50`).  Also note that `crate_epoch` doesn't change during a minor
+# version update (such as the one that this script produces in `auto` and
+# `single` modes).
 
 THIS_DIR = os.path.dirname(__file__)
 CHROMIUM_DIR = os.path.abspath(os.path.join(THIS_DIR, '..', '..'))
@@ -86,32 +86,100 @@
     return RunCommandAndCheckForErrors([RUN_GNRT] + list(args), True)
 
 
-def GetCurrentCrateVersions() -> Dict[str, str]:
-    """Parses Cargo.lock and returns a dictionary from crate epochs to versions
-    (e.g. "syn@v2" -> "2.0.50")."""
+def GetCurrentCrateIds() -> Set[str]:
+    """Parses Cargo.lock and returns a set of crate ids
+    (e.g. "serde@1.0.197", "syn@2.0.50", ...)."""
     t = toml.load(open(CARGO_LOCK))
-    result = dict()
+    result = set()
     for p in t["package"]:
         name = p["name"]
         version = p["version"]
-        key = GetCrateEpoch(name, version)
-        assert key not in result
-        result[key] = version
+        crate_id = f"{name}@{version}"
+        assert crate_id not in result
+        result.add(crate_id)
     return result
 
 
-def DiffCrateVersions(old_versions: Dict[str, str],
-                      new_versions: Dict[str, str]) -> Set[str]:
-    """Compares two results of `GetCurrentCrateVersions` and returns a `set` of
-    old crate ids (e.g. "syn@2.0.50") that have a different minor version in
-    `new_versions`"""
-    result = set()
-    for crate_epoch, old_version in old_versions.items():
-        new_version = new_versions.get(crate_epoch)
-        if new_version and old_version != new_version:
-            name = ConvertCrateIdToCrateName(crate_epoch)
-            result.add(f"{name}@{old_version}")
-    return result
+@dataclass(eq=True, order=True)
+class UpdatedCrate:
+    old_crate_id: str
+    new_crate_id: str
+
+    def __str__(self):
+        name = ConvertCrateIdToCrateName(self.old_crate_id)
+        assert name == ConvertCrateIdToCrateName(self.new_crate_id)
+
+        old_version = ConvertCrateIdToCrateVersion(self.old_crate_id)
+        new_version = ConvertCrateIdToCrateVersion(self.new_crate_id)
+
+        return f"{name}: {old_version} => {new_version}"
+
+
+@dataclass
+class CratesDiff:
+    updates: List[UpdatedCrate]
+    removed_crate_ids: List[str]
+    added_crate_ids: List[str]
+
+
+def DiffCrateIds(old_crate_ids: Set[str],
+                 new_crate_ids: Set[str],
+                 only_minor_updates=True) -> CratesDiff:
+    """Compares two results of `GetCurrentCrateIds` and returns what changed.
+    When `only_minor_updates` is True, then `foo@1.0` => `foo@2.0` will be
+    treated as a removal of `foo@1.0` and an addition of `foo@2.0`.  Otherwise,
+    it will be treated as an update.
+    """
+
+    def CrateIdsToDict(crate_ids: Set[str],
+                       only_minor_updates) -> Dict[str, str]:
+        """Transforms `crate_ids` into a dictionary that maps either 1) a crate
+        name (when `only_minor_updates=False`) or 2) a crate epoch (when
+        `only_minor_updates=True`) into 3) a crate version.  The caller should
+        treat the key format as an opaque implementation detail and just assume
+        that it will be stable for tracking a crate version across updates."""
+        result = dict()
+        for crate_id in crate_ids:
+            name = ConvertCrateIdToCrateName(crate_id)
+            version = ConvertCrateIdToCrateVersion(crate_id)
+            if only_minor_updates:
+                key = GetCrateEpoch(name, version)
+            else:
+                key = name
+            if key in result:
+                # No conflicts expected in `auto` or `single` mode.
+                assert not only_minor_updates
+                old_crate_id = result[key]
+                new_crate_id = crate_id
+                raise RuntimeError(f"Error calculating a `Cargo.lock` diff:" + \
+                                   f" conflict between {old_crate_id} and " + \
+                                   f"{new_crate_id}")
+            result[key] = crate_id
+        return result
+
+    # Ignoring `unchanged_ids` limits the situations when the key of `syn@1.x.x`
+    # may conflict with the key of `syn@2.x.x`.
+    unchanged_ids = new_crate_ids & old_crate_ids
+    old_dict = CrateIdsToDict(old_crate_ids - unchanged_ids, only_minor_updates)
+    new_dict = CrateIdsToDict(new_crate_ids - unchanged_ids, only_minor_updates)
+
+    updates = list()
+    removed_crate_ids = list()
+    for key, old_crate_id in old_dict.items():
+        new_crate_id = new_dict.get(key)
+        if new_crate_id:
+            assert old_crate_id != new_crate_id
+            updates.append(UpdatedCrate(old_crate_id, new_crate_id))
+        else:
+            removed_crate_ids.append(old_crate_id)
+
+    added_crate_ids = list()
+    for key, new_crate_id in new_dict.items():
+        if key not in old_dict:
+            added_crate_ids.append(new_crate_id)
+
+    return CratesDiff(sorted(updates), sorted(removed_crate_ids),
+                      sorted(added_crate_ids))
 
 
 def GetEpoch(crate_version: str) -> str:
@@ -146,10 +214,12 @@
     afterwards it runs `git reset --hard` to undo any changes.)"""
     print("Checking which crates can be updated...")
     assert not Git("status", "--porcelain")  # No local changes expected here.
-    old_versions = GetCurrentCrateVersions()
+    old_crate_ids = GetCurrentCrateIds()
     Gnrt("update")
-    crate_ids = DiffCrateVersions(old_versions, GetCurrentCrateVersions())
+    new_crate_ids = GetCurrentCrateIds()
     Git("reset", "--hard")
+    diff = DiffCrateIds(old_crate_ids, new_crate_ids)
+    crate_ids = [update.old_crate_id for update in diff.updates]
     if crate_ids:
         names = sorted([ConvertCrateIdToCrateName(id) for id in crate_ids])
         text = f"Found updates for {len(crate_ids)} crates: {', '.join(names)}"
@@ -163,11 +233,13 @@
     idempotent - at the end it runs `git reset --hard` to undo any changes.)"""
     print(f"Measuring the delta of updating {crate_id} to a newer version...")
     assert not Git("status", "--porcelain")  # No local changes expected here.
-    old_versions = GetCurrentCrateVersions()
+    old_crate_ids = GetCurrentCrateIds()
     Gnrt("update", crate_id)
-    update_size = len(DiffCrateVersions(old_versions,
-                                        GetCurrentCrateVersions()))
+    new_crate_ids = GetCurrentCrateIds()
     Git("reset", "--hard")
+    diff = DiffCrateIds(old_crate_ids, new_crate_ids)
+    update_size = len(diff.updates) + len(diff.added_crate_ids) + len(
+        diff.removed_crate_ids)
     return update_size
 
 
@@ -227,15 +299,15 @@
     return description
 
 
-def CreateCommitDescription(crate_id: str, old_versions: Dict[str, str],
-                            new_versions: Dict[str, str],
+def CreateCommitDescription(main_old_crate_id: str, diff: CratesDiff,
                             include_vet_criteria: bool) -> str:
-    crate_name = ConvertCrateIdToCrateName(crate_id)
-    old_version = ConvertCrateIdToCrateVersion(crate_id)
-    crate_epoch = GetCrateEpoch(crate_name, old_version)
-    new_version = new_versions[crate_epoch]
-    assert crate_epoch == GetCrateEpoch(crate_name, new_version)
-    roll_summary = f"{crate_name}: {old_version} => {new_version}"
+    main_crate_name = ConvertCrateIdToCrateName(main_old_crate_id)
+    main_old_version = ConvertCrateIdToCrateVersion(main_old_crate_id)
+    main_update = next(
+        filter(lambda u: u.old_crate_id == main_old_crate_id, diff.updates))
+    main_new_version = ConvertCrateIdToCrateVersion(main_update.new_crate_id)
+    roll_summary = f"{main_crate_name}: " + \
+        f"{main_old_version} => {main_new_version}"
     description = f"""Roll {roll_summary} in //third_party/rust.
 
 This CL has been created semi-automatically.  The expected review
@@ -243,28 +315,10 @@
 //tools/crates/create_update_cl.md
 """
 
-    new_or_updated_crate_ids = []
-    update_descriptions = []
-    new_crate_descriptions = []
-    for new_epoch, new_version in new_versions.items():
-        name = ConvertCrateIdToCrateName(new_epoch)
-        if new_epoch in old_versions:
-            old_version = old_versions[new_epoch]
-            if old_version != new_version:
-                new_or_updated_crate_ids.append(f'{name}@{new_version}')
-                update_descriptions.append(
-                    f"{name}: {old_version} => {new_version}")
-        else:
-            new_or_updated_crate_ids.append(f'{name}@{new_version}')
-            new_crate_descriptions.append(f"{name} {new_version}")
-    removed_crate_descriptions = []
-    for old_epoch, old_version in old_versions.items():
-        if old_epoch not in new_versions:
-            name = ConvertCrateIdToCrateName(old_epoch)
-            removed_crate_descriptions.append(f"{name} {old_version}")
-    update_descriptions = SortedMarkdownList(update_descriptions)
-    new_crate_descriptions = SortedMarkdownList(new_crate_descriptions)
-    removed_crate_descriptions = SortedMarkdownList(removed_crate_descriptions)
+    update_descriptions = SortedMarkdownList(
+        [str(update) for update in diff.updates])
+    new_crate_descriptions = SortedMarkdownList(diff.added_crate_ids)
+    removed_crate_descriptions = SortedMarkdownList(diff.removed_crate_ids)
     assert (update_descriptions)
     description += f"\nUpdated crates:\n\n{update_descriptions}\n"
     if new_crate_descriptions:
@@ -272,6 +326,8 @@
     if removed_crate_descriptions:
         description += f"\nRemoved crates:\n\n{removed_crate_descriptions}\n"
 
+    new_or_updated_crate_ids = diff.added_crate_ids + \
+        [update.new_crate_id for update in diff.updates]
     if include_vet_criteria:
         vet_policies = CreateVetPolicyDescription(new_or_updated_crate_ids)
         description += f"\n{vet_policies}"
@@ -301,16 +357,16 @@
     assert not Git("status", "--porcelain")  # No local changes expected here.
 
     # gnrt update
-    old_versions = GetCurrentCrateVersions()
+    old_crate_ids = GetCurrentCrateIds()
     print(f"  Running `gnrt update {crate_id}` ...")
     Gnrt("update", crate_id)
-    new_versions = GetCurrentCrateVersions()
-    if old_versions == new_versions:
+    new_crate_ids = GetCurrentCrateIds()
+    if old_crate_ids == new_crate_ids:
         print("  `gnrt update` resulted in no changes - "\
               "maybe other steps will handle this crate...")
         return upstream_branch
-    description = CreateCommitDescription(crate_id, old_versions, new_versions,
-                                          False)
+    diff = DiffCrateIds(old_crate_ids, new_crate_ids)
+    description = CreateCommitDescription(crate_id, diff, False)
 
     # Checkout a new git branch + `git cl upload`
     new_branch = f"{BRANCH_BASENAME}--{crate_id.replace('@', '-')}"
@@ -327,16 +383,15 @@
 
     # git mv <vendor/old version> <vendor/new version>
     print(f"  Running `git mv <vendor/old version> <vendor/new version>`...")
-    for new_epoch, new_version in new_versions.items():
-        if new_epoch in old_versions:
-            old_version = old_versions[new_epoch]
-            if old_version != new_version:
-                crate_name = ConvertCrateIdToCrateName(new_epoch)
-                old_dir = os.path.join(VENDOR_DIR,
-                                       f"{crate_name}-{old_version}")
-                new_dir = os.path.join(VENDOR_DIR,
-                                       f"{crate_name}-{new_version}")
-                Git("mv", "--", old_dir, new_dir)
+    for update in diff.updates:
+        crate_name = ConvertCrateIdToCrateName(update.old_crate_id)
+        assert crate_name == ConvertCrateIdToCrateName(update.new_crate_id)
+
+        old_version = ConvertCrateIdToCrateVersion(update.old_crate_id)
+        new_version = ConvertCrateIdToCrateVersion(update.new_crate_id)
+        old_dir = os.path.join(VENDOR_DIR, f"{crate_name}-{old_version}")
+        new_dir = os.path.join(VENDOR_DIR, f"{crate_name}-{new_version}")
+        Git("mv", "--", old_dir, new_dir)
     Git("add", "-f", "third_party/rust")
     Git("commit", "-m", "git mv <old dir> <new dir> (for better diff)")
     if args.upload:
@@ -360,8 +415,7 @@
         print(f"  Running `git cl upload ...` ...")
         Git("cl", "upload", "--bypass-hooks", "--force", "-m", "gnrt vendor")
         print(f"  Running `git cl description ...` ...")
-        description = CreateCommitDescription(crate_id, old_versions,
-                                              new_versions, True)
+        description = CreateCommitDescription(crate_id, diff, True)
         Git("cl", "description", f"--new-description={description}")
 
     # gnrt gen
diff --git a/tools/crates/create_update_cl_unittests.py b/tools/crates/create_update_cl_unittests.py
new file mode 100755
index 0000000..d222dbf
--- /dev/null
+++ b/tools/crates/create_update_cl_unittests.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env vpython3
+
+# 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 unittest
+
+from create_update_cl import DiffCrateIds, GetEpoch, ConvertCrateIdToCrateName,\
+                             ConvertCrateIdToCrateVersion, SortedMarkdownList,\
+                             CreateCommitDescription
+
+
+class DiffCrateIdsTests(unittest.TestCase):
+
+    def testBasicsViaMinorUpdateDetection(self):
+        before = set(
+            ["multi@1.0.1", "multi@2.0.1", "deleted@3.0.1", "single@4.0.1"])
+        after = set(
+            ["multi@1.0.1", "multi@2.0.2", "added@5.0.1", "single@4.0.2"])
+        diff = DiffCrateIds(before, after, only_minor_updates=True)
+        self.assertEqual(diff.added_crate_ids, ["added@5.0.1"])
+        self.assertEqual(diff.removed_crate_ids, ["deleted@3.0.1"])
+        self.assertEqual(len(diff.updates), 2)
+        self.assertEqual(diff.updates[0].old_crate_id, "multi@2.0.1")
+        self.assertEqual(diff.updates[0].new_crate_id, "multi@2.0.2")
+        self.assertEqual(diff.updates[1].old_crate_id, "single@4.0.1")
+        self.assertEqual(diff.updates[1].new_crate_id, "single@4.0.2")
+
+    def testMajorUpdateDetection(self):
+        before = set(["foo@1.0.1"])
+        after = set(["foo@2.0.2"])
+        diff = DiffCrateIds(before, after, only_minor_updates=False)
+        self.assertEqual(diff.added_crate_ids, [])
+        self.assertEqual(diff.removed_crate_ids, [])
+        self.assertEqual(len(diff.updates), 1)
+        self.assertEqual(diff.updates[0].old_crate_id, "foo@1.0.1")
+        self.assertEqual(diff.updates[0].new_crate_id, "foo@2.0.2")
+
+    def testMajorUpdateWithOnlyMinorUpdateDetection(self):
+        before = set(["foo@1.0.1"])
+        after = set(["foo@2.0.2"])
+        diff = DiffCrateIds(before, after, only_minor_updates=True)
+        self.assertEqual(diff.added_crate_ids, ["foo@2.0.2"])
+        self.assertEqual(diff.removed_crate_ids, ["foo@1.0.1"])
+        self.assertEqual(diff.updates, [])
+
+    def testNoChanges(self):
+        before = set(["foo@1.0.1"])
+        after = set(["foo@1.0.1"])
+        diff = DiffCrateIds(before, after, only_minor_updates=True)
+        self.assertEqual(diff.added_crate_ids, [])
+        self.assertEqual(diff.removed_crate_ids, [])
+        self.assertEqual(diff.updates, [])
+
+
+class CommitDescriptionTests(unittest.TestCase):
+
+    def testBasics(self):
+        before = set(["updated_crate@2.0.1", "deleted@3.0.1"])
+        after = set(["updated_crate@2.0.2", "added@5.0.1"])
+        diff = DiffCrateIds(before, after, only_minor_updates=True)
+
+        actual_desc = CreateCommitDescription("updated_crate@2.0.1", diff,
+                                              False)
+        expected_desc = \
+"""Roll updated_crate: 2.0.1 => 2.0.2 in //third_party/rust.
+
+This CL has been created semi-automatically.  The expected review
+process and other details can be found at
+//tools/crates/create_update_cl.md
+
+Updated crates:
+
+* updated_crate: 2.0.1 => 2.0.2
+
+New crates:
+
+* added@5.0.1
+
+Removed crates:
+
+* deleted@3.0.1
+
+Bug: None
+Cq-Include-Trybots: chromium/try:android-rust-arm32-rel
+Cq-Include-Trybots: chromium/try:android-rust-arm64-dbg
+Cq-Include-Trybots: chromium/try:android-rust-arm64-rel
+Cq-Include-Trybots: chromium/try:linux-rust-x64-dbg
+Cq-Include-Trybots: chromium/try:linux-rust-x64-rel
+Cq-Include-Trybots: chromium/try:win-rust-x64-dbg
+Cq-Include-Trybots: chromium/try:win-rust-x64-rel
+Disable-Rts: True
+"""
+        self.assertEqual(actual_desc, expected_desc)
+
+
+class OtherTests(unittest.TestCase):
+
+    def testGetEpoch(self):
+        self.assertEqual(GetEpoch("0.1.2"), "v0_1")
+        self.assertEqual(GetEpoch("1.2.3"), "v1")
+
+    def testConvertCrateIdToCrateName(self):
+        self.assertEqual(ConvertCrateIdToCrateName("foo-bar@1.2.3"), "foo-bar")
+
+    def testConvertCrateIdToCrateVersion(self):
+        self.assertEqual(ConvertCrateIdToCrateVersion("foo-bar@1.2.3"), "1.2.3")
+
+    def testSortedMarkdownList(self):
+        input = ["bbb " * 25, "aaa " * 30, "ccc " * 35]
+        actual_output = SortedMarkdownList(input)
+        expected_output = \
+"""* aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa
+  aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa
+* bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb
+  bbb bbb bbb bbb bbb bbb bbb bbb
+* ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc
+  ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc ccc
+  ccc"""
+        self.assertEqual(actual_output, expected_output)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index 7def8af..411280d1 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -81,12 +81,12 @@
     "includes": [2120],
   },
   "chrome/app/theme/google_chrome/chromeos/chromeos_chrome_internal_strings.grd": {
-    "messages": [2130],
+    "messages": [2140],
   },
 
   # Leave space for theme_resources since it has many structures.
   "chrome/app/theme/theme_resources.grd": {
-    "structures": [2140],
+    "structures": [2160],
   },
   # END chrome/app section.
 
@@ -101,635 +101,635 @@
     "structures": [2240],
   },
   "chrome/browser/nearby_sharing/internal/nearby_share_internal_icons.grd": {
-    "includes": [2250],
+    "includes": [2260],
   },
   "chrome/browser/nearby_sharing/internal/nearby_share_internal_strings.grd": {
-    "messages": [2260],
+    "messages": [2280],
   },
   "chrome/browser/platform_experience/win/resources/resources.grd": {
-    "includes": [2270],
-    "messages": [2275],
+    "includes": [2300],
+    "messages": [2320],
   },
   "chrome/browser/recent_tabs/internal/android/java/strings/android_restore_tabs_strings.grd": {
-    "messages": [2280],
+    "messages": [2340],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/accessibility/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [2300],
+    "includes": [2360],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/feedback/resources.grd": {
     "META": {"sizes": {"includes": [30],}},
-    "includes": [2320],
+    "includes": [2380],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/feed_internals/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [2340],
+    "includes": [2400],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/app_service_internals/resources.grd": {
     "META": {"sizes": {"includes": [5],}},
-    "includes": [2360],
+    "includes": [2420],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/bookmarks/resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [2380],
+    "includes": [2440],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/browser_switch/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [2400],
+    "includes": [2460],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/certificate_viewer/resources.grd": {
     "META": {"sizes": {"includes": [5],}},
-    "includes": [2420],
+    "includes": [2480],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/add_supervision/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [2440],
+    "includes": [2500],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/app_install/resources.grd": {
     "META": {"sizes": {"includes": [5]}},
-    "includes": [2450],
+    "includes": [2520],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/arc_account_picker/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [2460],
+    "includes": [2540],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/assistant_optin/assistant_optin_resources.grd": {
     "META": {"sizes": {"includes": [80]}},
-    "includes": [2480],
+    "includes": [2560],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/cloud_upload/resources.grd": {
     "META": {"sizes": {"includes": [50]}},
-    "includes": [2500],
+    "includes": [2580],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/desk_api/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [2520],
+    "includes": [2600],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/edu_coexistence/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [2540],
+    "includes": [2620],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/gaia_action_buttons/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [2560],
+    "includes": [2640],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/emoji_picker/resources.grd": {
     "META": {"sizes": {"includes": [60]}},
-    "includes": [2580],
+    "includes": [2660],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/kerberos/resources.grd": {
     "META": {"sizes": {"includes": [5],}},
-    "includes": [2600],
+    "includes": [2680],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/launcher_internals/resources.grd": {
     "META": {"sizes": {"includes": [50]}},
-    "includes": [2620],
+    "includes": [2700],
   },
    "chrome/browser/resources/chromeos/mako/resources.grd": {
     "META": {"sizes": {"includes": [150]}},
-    "includes": [2640],
+    "includes": [2720],
   },
   "chrome/browser/resources/chromeos/seal/resources.grd": {
     "META": {"sizes": {"includes": [50]}},
-    "includes": [2650],
+    "includes": [2740],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/parent_access/resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [2660],
+    "includes": [2760],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/remote_maintenance_curtain/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [2680],
+    "includes": [2780],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/sensor_info/resources.grd": {
     "META": {"sizes": {"includes": [50]}},
-    "includes": [2700],
+    "includes": [2800],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/supervision/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [2720],
+    "includes": [2820],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/predictors/resources.grd": {
     "META": {"sizes": {"includes": [5],}},
-    "includes": [2740],
+    "includes": [2840],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/profile_internals/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [2760],
+    "includes": [2860],
   },
   "chrome/browser/resources/app_icon/app_icon_resources.grd": {
-    "structures": [2780],
+    "structures": [2880],
   },
   "chrome/browser/resources/chromeos/app_icon/app_icon_resources.grd": {
-    "structures": [2800],
+    "structures": [2900],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/login/resources.grd": {
     "META": {"sizes": {"includes": [300],}},
-    "includes": [2820],
+    "includes": [2920],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/lock_screen_reauth/resources.grd": {
     "META": {"sizes": {"includes": [30]}},
-    "includes": [2860],
+    "includes": [2940],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/multidevice_internals/resources.grd": {
     "META": {"sizes": {"includes": [35]}},
-    "includes": [2900],
+    "includes": [2960],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [2920],
+    "includes": [2980],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/notification_tester/resources.grd": {
     "META": {"sizes": {"includes": [5]}},
-    "includes": [2940],
+    "includes": [3000],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/password_change/resources.grd": {
     "META": {"sizes": {"includes": [30]}},
-    "includes": [2960],
+    "includes": [3020],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/vc_tray_tester/resources.grd": {
     "META": {"sizes": {"includes": [5]}},
-    "includes": [2980],
+    "includes": [3040],
   },
   "chrome/browser/resources/component_extension_resources.grd": {
-    "includes": [3040],
-    "structures": [3060],
+    "includes": [3060],
+    "structures": [3080],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/access_code_cast/resources.grd": {
     "META": {"sizes": {"includes": [50]}},
-    "includes": [3080],
+    "includes": [3100],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/commerce/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [3090],
+    "includes": [3120],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/compose/resources.grd": {
     "META": {"sizes": {"includes": [15]}},
-    "includes": [3100],
+    "includes": [3140],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/connectors_internals/resources.grd": {
     "META": {"sizes": {"includes": [15]}},
-    "includes": [3120],
+    "includes": [3160],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/data_sharing_internals/resources.grd": {
     "META": {"sizes": {"includes": [5]}},
-    "includes": [3125],
+    "includes": [3180],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/device_log/resources.grd": {
     "META": {"sizes": {"includes": [5],}},
-    "includes": [3130],
+    "includes": [3200],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/discards/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [3140],
+    "includes": [3220],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/dlp_internals/resources.grd": {
     "META": {"sizes": {"includes": [15]}},
-    "includes": [3160],
+    "includes": [3240],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/downloads/resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [3180],
+    "includes": [3260],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/engagement/resources.grd": {
     "META": {"sizes": {"includes": [5],}},
-    "includes": [3200],
+    "includes": [3280],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/extensions/resources.grd": {
     "META": {"sizes": {"includes": [90],}},
-    "includes": [3220],
+    "includes": [3300],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/history/resources.grd": {
     "META": {"sizes": {"includes": [40]}},
-    "includes": [3240],
+    "includes": [3320],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/identity_internals/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [3260],
+    "includes": [3340],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/internals/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [3280],
+    "includes": [3360],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/intro/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [3300],
+    "includes": [3380],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/location_internals/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [3320],
+    "includes": [3400],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/management/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [3340],
+    "includes": [3420],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/memory_internals/resources.grd": {
     "META": {"sizes": {"includes": [5]}},
-    "includes": [3360],
+    "includes": [3440],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/new_tab_page_instant/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [3380],
+    "includes": [3460],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/nearby_internals/resources.grd": {
     "META": {"sizes": {"includes": [40]}},
-    "includes": [3400],
+    "includes": [3480],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/nearby_share/resources.grd": {
     "META": {"sizes": {"includes": [100]}},
-    "includes": [3420],
+    "includes": [3500],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/media_router/cast_feedback/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [3440],
+    "includes": [3520],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/media_router/internals/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [3460],
+    "includes": [3540],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/new_tab_page/resources.grd": {
     "META": {"sizes": {"includes": [200]}},
-    "includes": [3480],
+    "includes": [3560],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/new_tab_page_third_party/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [3500],
+    "includes": [3580],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/omnibox_popup/resources.grd": {
     "META": {"sizes": {"includes": [50]}},
-    "includes": [3520],
+    "includes": [3600],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/password_manager/resources.grd": {
     "META": {"sizes": {"includes": [200]}},
-    "includes": [3540],
+    "includes": [3620],
   },
   "chrome/browser/resources/preinstalled_web_apps/resources.grd": {
-    "includes": [3560],
+    "includes": [3640],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/pdf/resources.grd": {
     "META": {"sizes": {"includes": [200]}},
-    "includes": [3580],
+    "includes": [3660],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/print_preview/resources.grd": {
     "META": {"sizes": {"includes": [500],}},
-    "includes": [3600],
+    "includes": [3680],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/privacy_sandbox/resources.grd": {
     "META": {"sizes": {"includes": [30],}},
-    "includes": [3620],
+    "includes": [3700],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/sandbox_internals/resources.grd": {
     "META": {"sizes": {"includes": [5],}},
-    "includes": [3640],
+    "includes": [3720],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/segmentation_internals/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [3660],
+    "includes": [3740],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/bookmarks/resources.grd": {
     "META": {"sizes": {"includes": [45],}},
-    "includes": [3680],
+    "includes": [3760],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/commerce/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [3700],
+    "includes": [3780],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/companion/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [3720],
+    "includes": [3800],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/customize_chrome/resources.grd": {
     "META": {"sizes": {"includes": [60],}},
-    "includes": [3740],
+    "includes": [3820],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/history_clusters/resources.grd": {
     "META": {"sizes": {"includes": [5],}},
-    "includes": [3760],
+    "includes": [3840],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/read_anything/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [3780],
+    "includes": [3860],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/reading_list/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [3800],
+    "includes": [3880],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/shared/resources.grd": {
     "META": {"sizes": {"includes": [15],}},
-    "includes": [3820],
+    "includes": [3900],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/user_notes/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [3840],
+    "includes": [3920],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/performance_controls/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [3860],
+    "includes": [3940],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/ash/extended_updates/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [3865],
+    "includes": [3960],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/ash/inline_login/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [3870],
+    "includes": [3980],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/ash/settings/resources.grd": {
     "META": {"sizes": {"includes": [1000],}},
-    "includes": [3880],
+    "includes": [4000],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/search_engine_choice/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [3900],
+    "includes": [4020],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/settings/resources.grd": {
     "META": {"sizes": {"includes": [500],}},
-    "includes": [3920],
+    "includes": [4040],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/settings_shared/resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [3940],
+    "includes": [4060],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/signin/profile_picker/resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [3960],
+    "includes": [4080],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/signin/resources.grd": {
     "META": {"sizes": {"includes": [60],}},
-    "includes": [3980],
+    "includes": [4100],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/support_tool/resources.grd": {
     "META": {"sizes": {"includes": [30]}},
-    "includes": [4000],
+    "includes": [4120],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/tab_search/resources.grd": {
     "META": {"sizes": {"includes": [60]}},
-    "includes": [4020],
+    "includes": [4140],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/tab_strip/resources.grd": {
     "META": {"sizes": {"includes": [30]}},
-    "includes": [4040],
+    "includes": [4160],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/welcome/resources.grd": {
-    "META": {"sizes": {"includes": [60]}},
-    "includes": [4060],
+    "META": {"sizes": {"includes": [70]}},
+    "includes": [4180],
   },
   "chrome/browser/test_dummy/internal/android/resources/resources.grd": {
-    "includes": [4080],
+    "includes": [4200],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/webui_gallery/resources.grd": {
     "META": {"sizes": {"includes": [90]}},
-    "includes": [4100],
+    "includes": [4220],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/web_app_internals/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [4120],
+    "includes": [4240],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/whats_new/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [4140],
+    "includes": [4260],
   },
   # END chrome/browser section.
 
   # START chrome/ WebUI resources section
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/browsing_topics/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [4160],
+    "includes": [4280],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/bluetooth_internals/resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [4180],
+    "includes": [4300],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/audio/resources.grd": {
     "META": {"sizes": {"includes": [30]}},
-    "includes": [4200],
+    "includes": [4320],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/bluetooth_pairing_dialog/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [4220],
+    "includes": [4340],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/borealis_installer/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [4240],
+    "includes": [4360],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/healthd_internals/resources.grd": {
     "META": {"sizes": {"includes": [50]}},
-    "includes": [4280],
+    "includes": [4380],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/internet_config_dialog/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [4300],
+    "includes": [4400],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/internet_detail_dialog/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [4320],
+    "includes": [4420],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/network_ui/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [4340],
+    "includes": [4440],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/set_time_dialog/resources.grd": {
     "META": {"sizes": {"includes": [5]}},
-    "includes": [4350],
+    "includes": [4460],
   },
   "<(SHARED_INTERMEDIATE_DIR)/components/commerce/core/internals/resources/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [4360],
+    "includes": [4480],
   },
   "<(SHARED_INTERMEDIATE_DIR)/components/flags_ui/resources/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [4380],
+    "includes": [4500],
   },
   "<(SHARED_INTERMEDIATE_DIR)/components/history_clusters/history_clusters_internals/resources/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [4400],
+    "includes": [4520],
   },
   "<(SHARED_INTERMEDIATE_DIR)/components/download/resources/download_internals/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [4420],
+    "includes": [4540],
   },
   "<(SHARED_INTERMEDIATE_DIR)/components/optimization_guide/optimization_guide_internals/resources/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [4440],
+    "includes": [4560],
   },
   "<(SHARED_INTERMEDIATE_DIR)/components/policy/resources/webui/resources.grd": {
     "META": {"sizes": {"includes": [30]}},
-    "includes": [4460],
+    "includes": [4580],
   },
   "<(SHARED_INTERMEDIATE_DIR)/components/version_ui/resources/resources.grd": {
     "META": {"sizes": {"includes": [5]}},
-    "includes": [4480],
+    "includes": [4600],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/about_sys/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [4500],
+    "includes": [4620],
   },
   "<(SHARED_INTERMEDIATE_DIR)/components/ukm/debug/resources.grd": {
     "META": {"sizes": {"includes": [5]}},
-    "includes": [4510],
+    "includes": [4640],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/app_home/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [4520],
+    "includes": [4660],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/components/resources.grd": {
     "META": {"sizes": {"includes": [5]}},
-    "includes": [4540],
+    "includes": [4680],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/gaia_auth_host/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [4560],
+    "includes": [4700],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/invalidations/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [4580],
+    "includes": [4720],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/media/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [4600],
+    "includes": [4740],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/net_internals/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [4620],
+    "includes": [4760],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/omnibox/resources.grd": {
     "META": {"sizes": {"includes": [30]}},
-    "includes": [4640],
+    "includes": [4780],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/suggest_internals/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [4660],
+    "includes": [4800],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/quota_internals/quota_internals_resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [4680],
+    "includes": [4820],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/sync_file_system_internals/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [4700],
+    "includes": [4840],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/usb_internals/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [4720],
+    "includes": [4860],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/webapks/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [4740],
+    "includes": [4880],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/webui_js_error/resources.grd": {
    "META": {"sizes": {"includes": [10],}},
-   "includes": [4760],
+   "includes": [4900],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/app_settings/resources.grd": {
     "META": {"sizes": {"includes": [40]}},
-    "includes": [4780],
+    "includes": [4920],
   },
   "<(SHARED_INTERMEDIATE_DIR)/components/sync/service/resources/resources.grd": {
    "META": {"sizes": {"includes": [30],}},
-    "includes": [4800],
+    "includes": [4940],
   },
   "components/resources/dev_ui_components_resources.grd": {
-    "includes": [4820],
+    "includes": [4960],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/private_aggregation/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [4840],
+    "includes": [4980],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/attribution_reporting/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [4860],
+    "includes": [5000],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/gpu/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [4880],
+    "includes": [5020],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/histograms/resources.grd": {
     "META": {"sizes": {"includes": [5]}},
-    "includes": [4900],
+    "includes": [5040],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/indexed_db/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [4920],
+    "includes": [5060],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/media/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [4940],
+    "includes": [5080],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/net/resources.grd": {
     "META": {"sizes": {"includes": [5],}},
-    "includes": [4960],
+    "includes": [5100],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/process/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [4980],
+    "includes": [5120],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/service_worker/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [5000],
+    "includes": [5140],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/quota/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [5020],
+    "includes": [5160],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/traces_internals/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [5040],
+    "includes": [5180],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/webxr_internals/resources.grd": {
     "META": {"sizes": {"includes": [20,],}},
-    "includes": [5060],
+    "includes": [5200],
   },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/webrtc/resources/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [5080],
+    "includes": [5220],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/feed/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [5100],
+    "includes": [5240],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/manage_mirrorsync/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [5120],
+    "includes": [5260],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/inline_login/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [5140],
+    "includes": [5280],
   },
   "<(SHARED_INTERMEDIATE_DIR)/components/metrics/debug/resources.grd": {
     "META": {"sizes": {"includes": [15]}},
-    "includes": [5160],
+    "includes": [5300],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/lens/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [5180],
+    "includes": [5320],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/lens/overlay/resources.grd": {
-    "META": {"sizes": {"includes": [20]}},
-    "includes": [5200],
+    "META": {"sizes": {"includes": [30]}},
+    "includes": [5340],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/lens/overlay/search_bubble/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [5210],
+    "includes": [5360],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/enterprise_reporting/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [5220],
+    "includes": [5380],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/hats/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [5240],
+    "includes": [5400],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/on_device_internals/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [5260],
+    "includes": [5420],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/privacy_sandbox/internals/resources.grd": {
-   "META": {"sizes": {"includes": [25],}},
-    "includes": [5280],
+   "META": {"sizes": {"includes": [30],}},
+    "includes": [5440],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/key_value_pair_viewer_shared/resources.grd": {
-   "META": {"sizes": {"includes": [5]}},
-    "includes": [5295],
+   "META": {"sizes": {"includes": [10]}},
+    "includes": [5460],
   },
   # END chrome/ WebUI resources section
 
@@ -737,23 +737,23 @@
   "chrome/common/common_resources.grd": {
     # Big alignment at start of section.
     "META": {"align": 100},
-    "includes": [5300],
+    "includes": [5500],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/common/chromeos/extensions/chromeos_system_extensions_resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [5320],
+    "includes": [5520],
   },
   "chrome/credential_provider/gaiacp/gaia_resources.grd": {
-    "includes": [5340],
-    "messages": [5360],
+    "includes": [5540],
+    "messages": [5560],
   },
   "chrome/renderer/resources/renderer_resources.grd": {
-    "includes": [5380],
-    "structures": [5400],
+    "includes": [5580],
+    "structures": [5600],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/test/data/webui/resources.grd": {
     "META": {"sizes": {"includes": [2000],}},
-    "includes": [5420],
+    "includes": [5620],
   },
   # END chrome/ miscellaneous section.
 
@@ -761,67 +761,67 @@
   "chromeos/chromeos_strings.grd": {
     # Big alignment at start of section.
     "META": {"align": 100},
-    "messages": [5500],
+    "messages": [5700],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/ambient/resources/lottie_resources.grd": {
     "META": {"sizes": {"includes": [100],}},
-    "includes": [5520],
+    "includes": [5720],
   },
   "chromeos/ash/components/emoji/emoji.grd" : {
     "META": {"sizes": {"includes": [15],}},
-    "includes" : [5530],
+    "includes" : [5740],
   },
   "chromeos/ash/resources/ash_resources.grd": {
-    "includes": [5540],
+    "includes": [5760],
   },
   "chromeos/ash/resources/internal/ash_internal_strings.grd": {
-    "messages": [5550],
+    "messages": [5780],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/camera_app_ui/ash_camera_app_resources.grd": {
     "META": {"sizes": {"includes": [300],}},
-    "includes": [5560],
+    "includes": [5800],
   },
   "ash/webui/camera_app_ui/resources/strings/camera_strings.grd": {
-    "messages": [5580],
+    "messages": [5820],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/color_internals/resources/resources.grd": {
     "META": {"sizes": {"includes": [20],}},
-    "includes": [5600],
+    "includes": [5840],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/common/resources/office_fallback/resources.grd": {
     "META": {"sizes": {"includes": [5]}},
-    "includes": [5620],
+    "includes": [5860],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/conch/resources/resources.grd": {
     "META": {"sizes": {"includes": [200],}},
-    "includes": [5630],
+    "includes": [5880],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/connectivity_diagnostics/resources/resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [5640],
+    "includes": [5900],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/diagnostics_ui/resources/resources.grd": {
     "META": {"sizes": {"includes": [200],}},
-    "includes": [5660],
+    "includes": [5920],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/file_manager/resources/file_manager_swa_resources.grd": {
     "META": {"sizes": {"includes": [200]}},
-    "includes": [5680],
+    "includes": [5940],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/file_manager/untrusted_resources/file_manager_untrusted_resources.grd": {
     "META": {"sizes": {"includes": [20]}},
-    "includes": [5700],
+    "includes": [5960],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/files_internals/ash_files_internals_resources.grd": {
     "META": {"sizes": {"includes": [10],}},
-    "includes": [5720],
+    "includes": [5980],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/common/resources/resources.grd": {
     "META": {"sizes": {"includes": [1000]}},
-    "includes": [5760],
+    "includes": [6000],
   },
   "ash/webui/help_app_ui/resources/help_app_resources.grd": {
-    "includes": [5780],
+    "includes": [6020],
   },
   # Both help_app_kids_magazine_bundle_resources.grd and
   # help_app_kids_magazine_bundle_mock_resources.grd start with the same id
@@ -830,10 +830,10 @@
   # in this case (HTML, JS and CSS file).
   "ash/webui/help_app_ui/resources/prod/help_app_kids_magazine_bundle_resources.grd": {
     "META": {"sizes": {"includes": [15],}},
-    "includes": [5800],
+    "includes": [6040],
   },
   "ash/webui/help_app_ui/resources/mock/help_app_kids_magazine_bundle_mock_resources.grd": {
-    "includes": [5800],
+    "includes": [6040],
   },
   # Both help_app_bundle_resources.grd and help_app_bundle_mock_resources.grd
   # start with the same id because only one of them is built depending on if
@@ -842,14 +842,14 @@
   # and bundled content in the top 25 languages (25 x 2).
   "ash/webui/help_app_ui/resources/prod/help_app_bundle_resources.grd": {
     "META": {"sizes": {"includes": [300],}},  # Relies on src-internal.
-    "includes": [5820],
+    "includes": [6060],
   },
   "ash/webui/help_app_ui/resources/mock/help_app_bundle_mock_resources.grd": {
-    "includes": [5820],
+    "includes": [6060],
   },
   "ash/webui/media_app_ui/resources/media_app_resources.grd": {
     "META": {"join": 2},
-    "includes": [5840],
+    "includes": [6080],
   },
   # Both media_app_bundle_resources.grd and media_app_bundle_mock_resources.grd
   # start with the same id because only one of them is built depending on if
@@ -857,77 +857,77 @@
   # of languages (74).
   "ash/webui/media_app_ui/resources/prod/media_app_bundle_resources.grd": {
     "META": {"sizes": {"includes": [130],}},  # Relies on src-internal.
-    "includes": [5860],
+    "includes": [6100],
   },
   "ash/webui/media_app_ui/resources/mock/media_app_bundle_mock_resources.grd": {
-    "includes": [5860],
+    "includes": [6100],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/print_management/resources/resources.grd": {
     "META": {"join": 2, "sizes": {"includes": [20]}},
-    "includes": [5880],
+    "includes": [6120],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/print_preview_cros/resources/resources.grd": {
     "META": {"sizes": {"includes": [50]}},
-    "includes": [5885],
+    "includes": [6140],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/sample_system_web_app_ui/resources/trusted/resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [5900],
+    "includes": [6160],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/sample_system_web_app_ui/resources/untrusted/resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [5920],
+    "includes": [6180],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/scanning/resources/resources.grd": {
     "META": {"sizes": {"includes": [100],}},
-    "includes": [5940],
+    "includes": [6200],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/status_area_internals/resources/resources.grd": {
     "META": {"sizes": {"includes": [30],}},
-    "includes": [5960],
+    "includes": [6220],
   },
   "chromeos/resources/chromeos_resources.grd": {
-    "includes": [6000],
+    "includes": [6240],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/eche_app_ui/ash_eche_app_resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [6020],
+    "includes": [6260],
   },
   # Both ash_eche_bundle_resources.grd and ash_eche_bundle_mock_resources.grd
   # start with the same id because only one of them is built depending on if
   # src_internal is available.
   "ash/webui/eche_app_ui/resources/prod/ash_eche_bundle_resources.grd": {
     "META": {"sizes": {"includes": [120],}},
-    "includes": [6040],
+    "includes": [6280],
   },
   "ash/webui/eche_app_ui/resources/mock/ash_eche_bundle_mock_resources.grd": {
     "META": {"sizes": {"includes": [120],}},
-    "includes": [6040],
+    "includes": [6280],
   },
   "ash/webui/multidevice_debug/resources/multidevice_debug_resources.grd": {
     "META": {"join": 2},
-    "includes": [6060],
+    "includes": [6300],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/personalization_app/resources/resources.grd": {
     "META": {"sizes": {"includes": [200],}},
-    "includes": [6080],
+    "includes": [6320],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/demo_mode_app_ui/ash_demo_mode_app_resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-   "includes": [6100],
+   "includes": [6340],
   },
 
  "<(SHARED_INTERMEDIATE_DIR)/ash/webui/projector_app/resources/app/untrusted/ash_projector_app_untrusted_resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [6120],
+    "includes": [6360],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/projector_app/resources/annotator/untrusted/ash_projector_annotator_untrusted_resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [6140],
+    "includes": [6380],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/projector_app/resources/common/ash_projector_common_resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [6160],
+    "includes": [6400],
   },
 
   # Both projector_app_bundle_resources.grd and projector_app_bundle_mock_resources.grd
@@ -936,15 +936,15 @@
   # of languages (79).
   "ash/webui/projector_app/resources/prod/projector_app_bundle_resources.grd": {
     "META": {"sizes": {"includes": [120],}}, # Relies on src-internal.
-    "includes": [6180],
+    "includes": [6420],
   },
   "ash/webui/projector_app/resources/mock/projector_app_bundle_mock_resources.grd": {
-    "includes": [6180],
+    "includes": [6420],
   },
 
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/vc_background_ui/resources/resources.grd": {
     "META": {"join": 2, "sizes": {"includes": [50],}},
-    "includes": [6260],
+    "includes": [6440],
   },
   # END chromeos/ section.
 
@@ -1091,10 +1091,10 @@
   },
   "ios_internal/chrome/app/ios_internal_chromium_strings.grd": {
     "META": {"join": 2},
-    "messages": [7240],
+    "messages": [7280],
   },
   "ios_internal/chrome/app/ios_internal_google_chrome_strings.grd": {
-    "messages": [7240],
+    "messages": [7280],
   },
   # END ios_internal/ section.
 
@@ -1159,135 +1159,135 @@
     "structures": [7600],
   },
   "ash/system/mahi/resources/mahi_resources.grd": {
-    "structures":[7610],
+    "structures":[7620],
   },
   "base/tracing/protos/resources.grd": {
-    "includes": [7620],
+    "includes": [7640],
   },
   "chromecast/app/resources/chromecast_settings.grd": {
-    "messages": [7640],
+    "messages": [7660],
   },
   "chromecast/app/resources/shell_resources.grd": {
-    "includes": [7660],
+    "includes": [7680],
   },
   "chromecast/renderer/resources/extensions_renderer_resources.grd": {
-    "includes": [7680],
+    "includes": [7700],
   },
 
   "device/bluetooth/bluetooth_strings.grd": {
-    "messages": [7700],
-  },
-
-  "device/fido/fido_strings.grd": {
     "messages": [7720],
   },
 
+  "device/fido/fido_strings.grd": {
+    "messages": [7740],
+  },
+
   "extensions/browser/resources/extensions_browser_resources.grd": {
-    "structures": [7740],
+    "structures": [7760],
   },
   "extensions/extensions_resources.grd": {
-    "includes": [7760],
+    "includes": [7780],
   },
   "extensions/renderer/resources/extensions_renderer_resources.grd": {
-    "includes": [7780],
-    "structures": [7800],
+    "includes": [7800],
+    "structures": [7820],
   },
   "extensions/shell/app_shell_resources.grd": {
-    "includes": [7820],
+    "includes": [7840],
   },
   "extensions/strings/extensions_strings.grd": {
-    "messages": [7840],
+    "messages": [7860],
   },
 
   "mojo/public/js/mojo_bindings_resources.grd": {
-    "includes": [7860],
-  },
-
-  "net/base/net_resources.grd": {
     "includes": [7880],
   },
 
+  "net/base/net_resources.grd": {
+    "includes": [7900],
+  },
+
   "remoting/resources/remoting_strings.grd": {
-    "messages": [7900],
+    "messages": [7920],
   },
 
   "services/services_strings.grd": {
-    "messages": [7920],
+    "messages": [7940],
   },
   "third_party/blink/public/blink_image_resources.grd": {
-    "structures": [7940],
+    "structures": [7960],
   },
   "third_party/blink/public/blink_resources.grd": {
-    "includes": [7960],
+    "includes": [7980],
   },
   "third_party/blink/renderer/modules/media_controls/resources/media_controls_resources.grd": {
-    "includes": [7980],
-    "structures": [8000],
+    "includes": [8000],
+    "structures": [8020],
   },
   "third_party/blink/public/strings/blink_accessibility_strings.grd": {
-    "messages": [8020],
-  },
-  "third_party/blink/public/strings/blink_strings.grd": {
     "messages": [8040],
   },
-  "third_party/libaddressinput/chromium/address_input_strings.grd": {
+  "third_party/blink/public/strings/blink_strings.grd": {
     "messages": [8060],
   },
+  "third_party/libaddressinput/chromium/address_input_strings.grd": {
+    "messages": [8080],
+  },
 
   "ui/base/test/ui_base_test_resources.grd": {
-    "messages": [8080],
+    "messages": [8100],
   },
   "ui/chromeos/resources/ui_chromeos_resources.grd": {
-    "structures": [8100],
+    "structures": [8120],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ui/chromeos/styles/cros_typography_resources.grd": {
     "META": {"sizes": {"includes": [5],}},
-    "includes": [8120],
+    "includes": [8140],
   },
   "ui/chromeos/ui_chromeos_strings.grd": {
-    "messages": [8140],
+    "messages": [8160],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ui/file_manager/file_manager_gen_resources.grd": {
     "META": {"sizes": {"includes": [2000]}},
-    "includes": [8160],
-  },
-  "ui/file_manager/file_manager_resources.grd": {
     "includes": [8180],
   },
+  "ui/file_manager/file_manager_resources.grd": {
+    "includes": [8200],
+  },
   "ui/resources/ui_resources.grd": {
-    "structures": [8200],
+    "structures": [8220],
   },
   "ui/resources/ui_unscaled_resources.grd": {
-    "includes": [8220],
+    "includes": [8240],
   },
   "ui/strings/app_locale_settings.grd": {
-    "messages": [8240],
-  },
-  "ui/strings/ax_strings.grd": {
     "messages": [8260],
   },
-  "ui/strings/ui_strings.grd": {
+  "ui/strings/ax_strings.grd": {
     "messages": [8280],
   },
-  "ui/views/examples/views_examples_resources.grd": {
+  "ui/strings/ui_strings.grd": {
     "messages": [8300],
   },
+  "ui/views/examples/views_examples_resources.grd": {
+    "messages": [8320],
+  },
   "ui/views/resources/views_resources.grd": {
-    "structures": [8320],
+    "structures": [8340],
   },
   "ui/webui/examples/resources/webui_examples_resources.grd": {
-    "messages": [8340],
+    "messages": [8360],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ui/webui/examples/resources/browser/resources.grd": {
     "META": {"sizes": {"includes": [10]}},
-    "includes": [8360],
+    "includes": [8380],
   },
   "<(SHARED_INTERMEDIATE_DIR)/ui/webui/resources/webui_resources.grd": {
     "META": {"sizes": {"includes": [1100]}},
-    "includes": [8380],
+    "includes": [8400],
   },
   "weblayer/weblayer_resources.grd": {
-    "includes": [8400],
+    "includes": [8420],
   },
 
   # This file is generated during the build.
@@ -1296,13 +1296,13 @@
     # In debug build, devtools frontend sources are not bundled and therefore
     # includes a lot of individual resources
     "META": {"sizes": {"includes": [2500],}},
-    "includes": [8420],
+    "includes": [8440],
   },
 
   # This file is generated during the build.
   "<(SHARED_INTERMEDIATE_DIR)/resources/inspector_overlay/inspector_overlay_resources.grd": {
     "META": {"sizes": {"includes": [50],}},
-    "includes": [8440],
+    "includes": [8460],
   },
 
   # END "everything else" section.
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 15f83d3..d211089d 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -19362,6 +19362,41 @@
   </description>
 </action>
 
+<action name="ManualFallback_VirtualCard_SelectCardholderName">
+  <owner>chbannon@google.com</owner>
+  <owner>siyua@chromium.org</owner>
+  <description>
+    The user tapped on the cardholder name of a virtual card in the Credit Card
+    Fallback view.
+  </description>
+</action>
+
+<action name="ManualFallback_VirtualCard_SelectCardNumber">
+  <owner>chbannon@google.com</owner>
+  <owner>siyua@chromium.org</owner>
+  <description>
+    The user tapped on a virtual card number in the Credit Card Fallback view.
+  </description>
+</action>
+
+<action name="ManualFallback_VirtualCard_SelectExpirationMonth">
+  <owner>chbannon@google.com</owner>
+  <owner>siyua@chromium.org</owner>
+  <description>
+    The user tapped on the expiration month of a virtual card in the Credit Card
+    Fallback view.
+  </description>
+</action>
+
+<action name="ManualFallback_VirtualCard_SelectExpirationYear">
+  <owner>chbannon@google.com</owner>
+  <owner>siyua@chromium.org</owner>
+  <description>
+    The user tapped on the expiration year of a virtual card in the Credit Card
+    Fallback view.
+  </description>
+</action>
+
 <action name="MaxButton_Clk_ExitFS">
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <description>Please enter the description of this user action.</description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 3d97d6bc..bd2c8a0 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -17126,6 +17126,8 @@
   <int value="-1775842908" label="EnableOAuthIpp:disabled"/>
   <int value="-1774921110" label="ServiceWorkerStaticRouter:enabled"/>
   <int value="-1774818943" label="VrWebInputEditing:enabled"/>
+  <int value="-1774392993"
+      label="CompressionDictionaryTransportOverHttp2:disabled"/>
   <int value="-1774290918" label="webview-shadow-dom-fenced-frames"/>
   <int value="-1773925297" label="TimedHtmlParserBudget:disabled"/>
   <int value="-1773078316" label="DragDropTabTearing:enabled"/>
@@ -20554,6 +20556,8 @@
   <int value="-241065111" label="BackForwardTransitions:disabled"/>
   <int value="-241061180" label="NotificationImageDrag:enabled"/>
   <int value="-240531943" label="ContextualSearchRankerQuery:disabled"/>
+  <int value="-240481438"
+      label="CompressionDictionaryTransportOverHttp2:enabled"/>
   <int value="-239616243" label="HighDynamicRange:enabled"/>
   <int value="-239176328" label="BluetoothAggressiveAppearanceFilter:enabled"/>
   <int value="-238717837" label="CellularUseSecondEuicc:disabled"/>
@@ -23439,6 +23443,7 @@
   <int value="1061351122" label="LinkLayerPrivacy:disabled"/>
   <int value="1061947013" label="OmniboxDeferredKeyboardPopup:disabled"/>
   <int value="1062357243" label="remember-cert-error-decisions"/>
+  <int value="1062563622" label="GateNV12GMBVideoFramesOnHWSupport:enabled"/>
   <int value="1063490936" label="ExoSurroundingTextOffset:enabled"/>
   <int value="1063909131" label="CrOSDspBasedAgcAllowed:disabled"/>
   <int value="1064288458" label="OfflineRecentPages:enabled"/>
@@ -24677,6 +24682,7 @@
   <int value="1616418306" label="AssistAutoCorrect:disabled"/>
   <int value="1616445483" label="OmniboxAnswerActions:disabled"/>
   <int value="1616782064" label="shelf-dense-clamshell"/>
+  <int value="1616881431" label="GateNV12GMBVideoFramesOnHWSupport:disabled"/>
   <int value="1616938915"
       label="OmniboxUIExperimentWhiteBackgroundOnBlur:disabled"/>
   <int value="1617187093" label="enable-improved-a2hs"/>
diff --git a/tools/metrics/histograms/metadata/android/enums.xml b/tools/metrics/histograms/metadata/android/enums.xml
index 267cfcf..d059676 100644
--- a/tools/metrics/histograms/metadata/android/enums.xml
+++ b/tools/metrics/histograms/metadata/android/enums.xml
@@ -1539,6 +1539,24 @@
   <int value="1" label="RightEdge"/>
 </enum>
 
+<enum name="TabGroupColorChangeActionType">
+  <int value="0" label="VIA_COLOR_ICON"/>
+  <int value="1" label="VIA_OVERFLOW_MENU"/>
+</enum>
+
+<enum name="TabGroupCreationDialogResultAction">
+  <int value="0" label="ACCEPTED"/>
+  <int value="1" label="DISMISSED_SCRIM_OR_BACKPRESS"/>
+  <int value="2" label="DISMISSED_OTHER"/>
+</enum>
+
+<enum name="TabGroupCreationFinalSelections">
+  <int value="0" label="DEFAULT_COLOR_AND_TITLE"/>
+  <int value="1" label="CHANGED_COLOR"/>
+  <int value="2" label="CHANGED_TITLE"/>
+  <int value="3" label="CHANGED_COLOR_AND_TITLE"/>
+</enum>
+
 <enum name="TabListEditorShareActionState">
   <int value="0" label="UNKNOWN_SHARE_STATE"/>
   <int value="1" label="SUCCESSFULLY_SHARED_TABS"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index c1bf43d3..c9cb3b2 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -693,7 +693,7 @@
 </histogram>
 
 <histogram name="Android.BindingManger.ConnectionsDroppedDueToMaxSize"
-    units="connections" expires_after="2024-04-24">
+    units="connections" expires_after="2024-10-24">
   <owner>ckitagawa@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -705,7 +705,7 @@
 </histogram>
 
 <histogram name="Android.ChildProcessBinding.TotalConnections"
-    units="connections" expires_after="2024-08-20">
+    units="connections" expires_after="2024-10-24">
   <owner>ckitagawa@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -718,7 +718,7 @@
 
 <histogram
     name="Android.ChildProcessBinding.{ChildProcessConnectionMetricsBindingState}Connections"
-    units="connections" expires_after="2024-08-20">
+    units="connections" expires_after="2024-10-24">
   <owner>ckitagawa@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -4053,6 +4053,42 @@
   </token>
 </histogram>
 
+<histogram name="Android.TabGroupParity.TabGroupColorChangeActionType"
+    enum="TabGroupColorChangeActionType" expires_after="2025-04-30">
+  <owner>ckitagawa@chromium.org</owner>
+  <owner>bjfong@google.com</owner>
+  <owner>fredmello@chromium.org</owner>
+  <summary>
+    Reports the entrypoint from which a tab group's color icon was changed.
+    Record once per user attempt to change the tab group's color in the
+    TabGridDialog view.
+  </summary>
+</histogram>
+
+<histogram name="Android.TabGroupParity.TabGroupCreationDialogResultAction"
+    enum="TabGroupCreationDialogResultAction" expires_after="2025-04-30">
+  <owner>ckitagawa@chromium.org</owner>
+  <owner>bjfong@google.com</owner>
+  <owner>fredmello@chromium.org</owner>
+  <summary>
+    The result of the action taken from the TabGroupCreation dialog. Record once
+    per user attempt to create a new tab group through the TabGroupCreation
+    modal dialog.
+  </summary>
+</histogram>
+
+<histogram name="Android.TabGroupParity.TabGroupCreationFinalSelections"
+    enum="TabGroupCreationFinalSelections" expires_after="2025-04-30">
+  <owner>ckitagawa@chromium.org</owner>
+  <owner>bjfong@google.com</owner>
+  <owner>fredmello@chromium.org</owner>
+  <summary>
+    The result of the group color and title selections made when accepting the
+    TabGroupCreation dialog. Record once per user attempt to create a new tab
+    group through the TabGroupCreation modal dialog.
+  </summary>
+</histogram>
+
 <histogram name="Android.TabMultiSelectV2.BookmarkTabsCount" units="count"
     expires_after="2024-09-01">
   <owner>ckitagawa@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/apps/enums.xml b/tools/metrics/histograms/metadata/apps/enums.xml
index 814dd71d..0957e02 100644
--- a/tools/metrics/histograms/metadata/apps/enums.xml
+++ b/tools/metrics/histograms/metadata/apps/enums.xml
@@ -56,10 +56,9 @@
   <int value="4" label="kAppProviderNotAvailable"/>
   <int value="5" label="kAppTypeNotSupported"/>
   <int value="6" label="kInstallParametersInvalid"/>
-  <int value="7" label="kAppAlreadyInstalled"/>
-  <int value="8" label="kInstallDialogNotAccepted"/>
-  <int value="9" label="kAppTypeInstallFailed"/>
-  <int value="10" label="kUserTypeNotPermitted"/>
+  <int value="7" label="kInstallDialogNotAccepted"/>
+  <int value="8" label="kAppTypeInstallFailed"/>
+  <int value="9" label="kUserTypeNotPermitted"/>
 </enum>
 
 <enum name="AppListAppCollections">
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 1fc0066..46730d6 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -8516,6 +8516,16 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.Wallpaper.SeaPen.Template.Settled" enum="SeaPenTemplateId"
+    expires_after="2025-04-10">
+  <owner>thuongphan@chromium.org</owner>
+  <owner>assistive-eng@google.com</owner>
+  <summary>
+    The template of the Sea Pen image currently set as the active wallpaper.
+    Emitted once for every UMA upload.
+  </summary>
+</histogram>
+
 <histogram name="Ash.Wallpaper.Source2" enum="WallpaperType"
     expires_after="2024-09-01">
   <owner>thuongphan@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 900c0488..a1705c2 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -1126,7 +1126,7 @@
 
 <histogram
     name="Blink.DocumentLoader.CreateParserPostCommit.Time.OutermostMainFrame.NewNavigation.IsHTTPOrHTTPS"
-    units="ms" expires_after="2024-05-20">
+    units="ms" expires_after="2024-10-10">
   <owner>chikamune@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/chromeos/enums.xml b/tools/metrics/histograms/metadata/chromeos/enums.xml
index aef725ee..a1a8e3f3 100644
--- a/tools/metrics/histograms/metadata/chromeos/enums.xml
+++ b/tools/metrics/histograms/metadata/chromeos/enums.xml
@@ -1301,6 +1301,13 @@
   <int value="3" label="QA"/>
 </enum>
 
+<enum name="MahiMenuButton">
+  <int value="0" label="Summary Button"/>
+  <int value="1" label="Outline"/>
+  <int value="2" label="Submit Question Button"/>
+  <int value="3" label="Condensed Menu Button"/>
+</enum>
+
 <enum name="ModifierKeyDomCodes">
   <int value="0" label="MetaLeft"/>
   <int value="1" label="MetaRight"/>
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index c72ec8f..aad52a46 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -2269,6 +2269,15 @@
   </summary>
 </histogram>
 
+<histogram name="ChromeOS.Mahi.ContextMenuView.ButtonClicked"
+    enum="MahiMenuButton" expires_after="2025-04-09">
+  <owner>leandre@chromium.org</owner>
+  <owner>cros-status-area-eng@google.com</owner>
+  <summary>
+    Recorded when any button in the web Mahi Context Menu view is clicked.
+  </summary>
+</histogram>
+
 <histogram name="ChromeOS.MessageCenter.ScrollActionReason"
     enum="ChromeOSMessageCenterScrollActionReason" expires_after="2024-07-28">
   <owner>leandre@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/content/histograms.xml b/tools/metrics/histograms/metadata/content/histograms.xml
index 0ba85b5..3967e55 100644
--- a/tools/metrics/histograms/metadata/content/histograms.xml
+++ b/tools/metrics/histograms/metadata/content/histograms.xml
@@ -153,6 +153,16 @@
   <summary>The status of the push notifications client for Content.</summary>
 </histogram>
 
+<histogram name="ContentNotifications.OpenURLAction.HasURL" enum="Boolean"
+    expires_after="2024-10-10">
+  <owner>guiperez@google.com</owner>
+  <owner>chrome-sherlock@google.com</owner>
+  <summary>
+    After a notification interaction, if the URL has successfully been parsed or
+    if it has failed.
+  </summary>
+</histogram>
+
 <histogram name="ContentNotifications.Promo.Prompt.Action"
     enum="ContentNotificationPromptAction" expires_after="2024-06-20">
   <owner>tinazwang@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/cros_audio/enums.xml b/tools/metrics/histograms/metadata/cros_audio/enums.xml
index 7ff33a02..96f9204 100644
--- a/tools/metrics/histograms/metadata/cros_audio/enums.xml
+++ b/tools/metrics/histograms/metadata/cros_audio/enums.xml
@@ -89,6 +89,29 @@
       label="Output|User overrode|System didn't switch|Non Chrome restarts"/>
 </enum>
 
+<enum name="AudioSelectionExceptionRules">
+  <int value="0" label="Input|Rule#1|Hot plugging privileged devices"/>
+  <int value="1" label="Output|Rule#1|Hot plugging privileged devices"/>
+  <int value="2"
+      label="Input|Rule#2|Unplugging a non active device keeps current active
+             device unchanged"/>
+  <int value="3"
+      label="Output|Rule#2|Unplugging a non active device keeps current
+             active device unchanged"/>
+  <int value="4"
+      label="Input|Rule#3|Hot plugging an unpreferred device keeps current
+             active device unchanged"/>
+  <int value="5"
+      label="Output|Rule#3|Hot plugging an unpreferred device keeps current
+             active device unchanged"/>
+  <int value="6"
+      label="Input|Rule#4|Unplugging a device causes remaining unseen set of
+             devices"/>
+  <int value="7"
+      label="Output|Rule#4|Unplugging a device causes remaining unseen set of
+             devices"/>
+</enum>
+
 </enums>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/cros_audio/histograms.xml b/tools/metrics/histograms/metadata/cros_audio/histograms.xml
index 28b30ae4..ae05ab37 100644
--- a/tools/metrics/histograms/metadata/cros_audio/histograms.xml
+++ b/tools/metrics/histograms/metadata/cros_audio/histograms.xml
@@ -36,6 +36,16 @@
   </summary>
 </histogram>
 
+<histogram name="ChromeOS.AudioSelection.ExceptionRulesMet"
+    enum="AudioSelectionExceptionRules" expires_after="2025-03-31">
+  <owner>zhangwenyu@google.com</owner>
+  <owner>cros-peripherals@google.com</owner>
+  <summary>
+    Recorded when audio selection exception rules are met. Exception rules are
+    detailed in enum AudioSelectionExceptionRules.
+  </summary>
+</histogram>
+
 <histogram
     name="ChromeOS.AudioSelection.{AudioType}.ConsecutiveDevicesChangeTimeElapsed.{ChangeType}"
     units="seconds" expires_after="2025-02-25">
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index b598afb..91a8e74 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -1746,6 +1746,27 @@
   </summary>
 </histogram>
 
+<histogram name="History.Embeddings.NumMatchedUrlsVisible" units="counts"
+    expires_after="2024-12-31">
+  <owner>sophiechang@chromium.org</owner>
+  <owner>zekunjiang@google.com</owner>
+  <summary>
+    Number of URLs that matched the query and are allowed to be shown based on
+    the content visibility policy. Logged each time a query leveraging history
+    embeddings is performed.
+  </summary>
+</histogram>
+
+<histogram name="History.Embeddings.NumUrlsMatched" units="counts"
+    expires_after="2024-12-31">
+  <owner>sophiechang@chromium.org</owner>
+  <owner>zekunjiang@google.com</owner>
+  <summary>
+    Number of URLs that matched the query. Logged each time a query leveraging
+    history embeddings is performed.
+  </summary>
+</histogram>
+
 <histogram name="History.Embeddings.Passages.ExtractionTime" units="ms"
     expires_after="2024-12-31">
   <owner>orinj@chromium.org</owner>
@@ -1788,6 +1809,17 @@
   </summary>
 </histogram>
 
+<histogram name="History.Embeddings.VisibilityModelAvailableAtQuery"
+    enum="Boolean" expires_after="2024-12-31">
+  <owner>sophiechang@chromium.org</owner>
+  <owner>zekunjiang@google.com</owner>
+  <summary>
+    Whether the visibility model was available to determine whether the search
+    results should be shown to the user for a given query. Logged each time a
+    query leveraging history embeddings is performed.
+  </summary>
+</histogram>
+
 <histogram name="History.FaviconDatabaseSizeMB" units="MB" expires_after="M130">
   <owner>rogerm@chromium.org</owner>
   <owner>sky@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/magic_stack/histograms.xml b/tools/metrics/histograms/metadata/magic_stack/histograms.xml
index 57a7debb..88e425e 100644
--- a/tools/metrics/histograms/metadata/magic_stack/histograms.xml
+++ b/tools/metrics/histograms/metadata/magic_stack/histograms.xml
@@ -130,7 +130,7 @@
 </histogram>
 
 <histogram
-    name="MagicStack.Clank.{ModuleDelegateHost}.ContextMenu.RemoveModule"
+    name="MagicStack.Clank.{ModuleDelegateHost}.ContextMenu.RemoveModule."
     enum="ModuleType" expires_after="2024-06-20">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
@@ -138,11 +138,27 @@
     Record the total count that the context menu item to remove a module is
     clicked. The histogram is logged on a {ModuleDelegateHost} and is logged
     Android-only.
+
+    Deprecated on 2024-04-10; prefer
+    MagicStack.Clank.{ModuleDelegateHost}.ContextMenu.RemoveModuleV2.
+  </summary>
+  <token key="ModuleDelegateHost" variants="ModuleDelegateHost"/>
+</histogram>
+
+<histogram
+    name="MagicStack.Clank.{ModuleDelegateHost}.ContextMenu.RemoveModuleV2"
+    enum="ModuleType" expires_after="2024-09-22">
+  <owner>hanxi@chromium.org</owner>
+  <owner>xinyiji@chromium.org</owner>
+  <summary>
+    Record the total count that the context menu item to remove a module is
+    clicked. The histogram is logged on a {ModuleDelegateHost} and is logged
+    Android-only.
   </summary>
   <token key="ModuleDelegateHost" variants="ModuleDelegateHost"/>
 </histogram>
 
-<histogram name="MagicStack.Clank.{ModuleDelegateHost}.ContextMenu.Shown"
+<histogram name="MagicStack.Clank.{ModuleDelegateHost}.ContextMenu.Shown."
     enum="ModuleType" expires_after="2024-06-20">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
@@ -150,6 +166,21 @@
     Record the total count of times that the context menu of a module is shown
     on the magic stack of a {ModuleDelegateHost}. The histogram is logged on
     Android-only.
+
+    Deprecated on 2024-04-10; prefer
+    MagicStack.Clank.{ModuleDelegateHost}.ContextMenu.ShownV2.
+  </summary>
+  <token key="ModuleDelegateHost" variants="ModuleDelegateHost"/>
+</histogram>
+
+<histogram name="MagicStack.Clank.{ModuleDelegateHost}.ContextMenu.ShownV2"
+    enum="ModuleType" expires_after="2024-09-22">
+  <owner>hanxi@chromium.org</owner>
+  <owner>xinyiji@chromium.org</owner>
+  <summary>
+    Record the total count of times that the context menu of a module is shown
+    on the magic stack of a {ModuleDelegateHost}. The histogram is logged on
+    Android-only.
   </summary>
   <token key="ModuleDelegateHost" variants="ModuleDelegateHost"/>
 </histogram>
@@ -227,7 +258,7 @@
 </histogram>
 
 <histogram
-    name="MagicStack.Clank.{ModuleDelegateHost}.Module.FetchDataTimeoutType"
+    name="MagicStack.Clank.{ModuleDelegateHost}.Module.FetchDataTimeoutType."
     enum="ModuleType" expires_after="2024-06-20">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
@@ -235,6 +266,22 @@
     Records the types of modules which didn't respond before the timer timeout.
     The histogram is logged when showing the magic stack on
     {ModuleDelegateHost}, Android-only.
+
+    Deprecated on 2024-04-10; prefer
+    MagicStack.Clank.{ModuleDelegateHost}.Module.FetchDataTimeoutTypeV2.
+  </summary>
+  <token key="ModuleDelegateHost" variants="ModuleDelegateHost"/>
+</histogram>
+
+<histogram
+    name="MagicStack.Clank.{ModuleDelegateHost}.Module.FetchDataTimeoutTypeV2"
+    enum="ModuleType" expires_after="2024-09-22">
+  <owner>hanxi@chromium.org</owner>
+  <owner>xinyiji@chromium.org</owner>
+  <summary>
+    Records the types of modules which didn't respond before the timer timeout.
+    The histogram is logged when showing the magic stack on
+    {ModuleDelegateHost}, Android-only.
   </summary>
   <token key="ModuleDelegateHost" variants="ModuleDelegateHost"/>
 </histogram>
@@ -266,13 +313,27 @@
   <token key="ModuleDelegateHost" variants="ModuleDelegateHost"/>
 </histogram>
 
-<histogram name="MagicStack.Clank.{ModuleDelegateHost}.Module.TopImpression"
+<histogram name="MagicStack.Clank.{ModuleDelegateHost}.Module.TopImpression."
     enum="ModuleType" expires_after="2024-06-20">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
   <summary>
     Record the total count of times that a module is shown on the magic stack of
     a {ModuleDelegateHost}. The histogram is logged on Android-only.
+
+    Deprecated on 2024-04-10; prefer
+    MagicStack.Clank.{ModuleDelegateHost}.Module.TopImpressionV2.
+  </summary>
+  <token key="ModuleDelegateHost" variants="ModuleDelegateHost"/>
+</histogram>
+
+<histogram name="MagicStack.Clank.{ModuleDelegateHost}.Module.TopImpressionV2"
+    enum="ModuleType" expires_after="2024-09-22">
+  <owner>hanxi@chromium.org</owner>
+  <owner>xinyiji@chromium.org</owner>
+  <summary>
+    Record the total count of times that a module is shown on the magic stack of
+    a {ModuleDelegateHost}. The histogram is logged on Android-only.
   </summary>
   <token key="ModuleDelegateHost" variants="ModuleDelegateHost"/>
 </histogram>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 37e4d453..d73ea971 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -2923,7 +2923,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ClientSideMtu" units="bytes"
-    expires_after="2024-04-03">
+    expires_after="2025-04-10">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -3670,7 +3670,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.MtuProbesSent" units="units"
-    expires_after="2024-04-03">
+    expires_after="2025-04-10">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -4159,12 +4159,13 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ServerSideMtu" units="bytes"
-    expires_after="2023-11-19">
+    expires_after="2025-04-10">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     The largest packet which the client received from the server during the
-    session.
+    session. Warning: this histogram was expired from 2023-11-19 to 2024-04-11;
+    data may be missing.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 66c5a81..3f79646 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -2219,8 +2219,8 @@
   <owner>alemate@chromium.org</owner>
   <owner>cros-oac@chromium.org</owner>
   <summary>
-    Time from the moment sign in authentication is started until the first user
-    session UI elements are painted. (Chrome OS).
+    Time from the moment sign in authentication is started until the post login
+    animation finishes. (Chrome OS).
   </summary>
 </histogram>
 
@@ -2240,8 +2240,8 @@
     Warning: this does not include the cryptohome authentication period, unlike
     BootTime.Login2.
 
-    TODO(b/328690294): we should have new metrics with the fix, and deprecate
-    this one.
+    TODO(b/323098858): deprecate this with Login4, once we have confirmed the
+    compositor animation waiting time is negligible.
   </summary>
 </histogram>
 
@@ -2263,7 +2263,8 @@
     Warning: this does not include the cryptohome authentication period, unlike
     BootTime.Login2.
 
-    TODO(b/328690294): deprecate with BootTime.Login3.
+    TODO(b/323098858): deprecate this with Login3, once we have confirmed the
+    compositor animation waiting time is negligible.
   </summary>
 </histogram>
 
@@ -6440,7 +6441,7 @@
 </histogram>
 
 <histogram base="true" name="GridTabSwitcher.MaxFrameInterval" units="ms"
-    expires_after="2024-04-28">
+    expires_after="2024-09-01">
   <owner>ckitagawa@chromium.org</owner>
   <owner>meiliang@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/quickstart/enums.xml b/tools/metrics/histograms/metadata/quickstart/enums.xml
index a7b6b97e..362df68d 100644
--- a/tools/metrics/histograms/metadata/quickstart/enums.xml
+++ b/tools/metrics/histograms/metadata/quickstart/enums.xml
@@ -121,8 +121,8 @@
   <int value="11" label="QS Screen: Getting Google Account Info"/>
   <int value="12" label="Quick Start Complete"/>
   <int value="13" label="Setup Device PIN"/>
-  <int value="14" label="Ask for Parent Permission"/>
-  <int value="15" label="Review Privacy and Terms"/>
+  <int value="14" label="Add Child"/>
+  <int value="15" label="(Unicorn) Review Privacy and Terms"/>
   <int value="16" label="Unified Setup"/>
   <int value="17" label="Gaia Info Screen"/>
   <int value="18" label="QS Screen: Wi-Fi Credentials Received"/>
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index 9b4bd693..ed9f31b 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -1446,6 +1446,33 @@
   <summary>[iOS] The number of pinned tabs opened at cold launch.</summary>
 </histogram>
 
+<histogram name="Tabs.PotentialDoubleDirty.SaveQueueSize" units="tabs"
+    expires_after="2024-09-01">
+  <owner>skym@chromium.org</owner>
+  <owner>ckitagawa@chroimum.org</owner>
+  <summary>
+    On Android, when a tab becomes dirty, it is queued to be saved to disk.
+    Clients that make multiple modifications in a sequence to the same tab can
+    cause poor performance by triggering a synchronous save from the first
+    modification, and then a second one from the others. This can be manually
+    worked around by notifying the TabStateAttributes, but clients that need
+    this change need to be identified first. This histogram is trying to help
+    identify the frequency that this situation is encountered.
+
+    This histogram, only on Android, is specifically recorded when we go to
+    queue a tab's data to be saved to persistent storage, and we notice that
+    we're also currently writing the same tab to disk. This is not always a
+    problem, as there could be many pending async writes happening to various
+    tabs, when new fresh modifications come in.
+
+    The value being recorded is the number of tabs in the save queue in front of
+    the tab being added. If the number is zero, it's very likely because of a
+    client making multiple sequential modifications. On the other hand, a larger
+    number is likely just the same tab being modified asynchronously and the
+    queue is backed up with other changes.
+  </summary>
+</histogram>
+
 <histogram
     name="Tabs.RecentlyClosed.EntriesRestoredInPage.{RecentlyClosedType}"
     units="entries restored" expires_after="2024-08-05">
diff --git a/tools/perf/contrib/shared_storage/shared_storage.py b/tools/perf/contrib/shared_storage/shared_storage.py
index 113fb18..4058660 100644
--- a/tools/perf/contrib/shared_storage/shared_storage.py
+++ b/tools/perf/contrib/shared_storage/shared_storage.py
@@ -18,6 +18,7 @@
 _ENABLED_FEATURES = [
     'SharedStorageAPI:ExposeDebugMessageForSettingsStatus/true',
     'SharedStorageAPIM118', 'SharedStorageAPIM124',
+    'SharedStorageAPIEnableWALForDatabase',
     'FencedFrames:implementation_type/mparch', 'FencedFramesDefaultMode',
     'PrivacySandboxAdsAPIsOverride', 'DefaultAllowPrivacySandboxAttestations'
 ]
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 215b1b6..4839fe7 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/v44.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "b6cb3dd90c5a594272b463b5cc9fef653380fa6f",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/5ce3ecd019fc89e40c7332e502d4f1aff4d5abb5/trace_processor_shell.exe"
+            "hash": "97a53a3244e906d406f6ff06ded7b41c701f8624",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/b1676f92d878b5bed2dbe493a9cbb9125a9ec2d5/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "d8e27d961be1db97db098c6826017aec0397ecfd",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v44.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "04d2149f018b6ebac3e1d987bca5708db37863ae",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/b65ac88b5b511bf489deb538194145530387e66e/trace_processor_shell"
+            "hash": "4d9068f40991a5714b5a14e44f6e23c8c3607a52",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/b1676f92d878b5bed2dbe493a9cbb9125a9ec2d5/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/rust/update_rust.py b/tools/rust/update_rust.py
index 2734a58..bb84a42 100755
--- a/tools/rust/update_rust.py
+++ b/tools/rust/update_rust.py
@@ -10,6 +10,7 @@
 '''
 
 import argparse
+import glob
 import os
 import re
 import shutil
@@ -126,8 +127,13 @@
     # downloading the toolchain if it hasn't changed, it also leads to multiple
     # versions of the same rustlibs. build/rust/std/find_std_rlibs.py chokes in
     # this case.
+    # .*_is_first_class_gcs file is created by first class GCS deps when rust
+    # hooks are migrated to be first class deps. In case we need to go back to
+    # using a hook, this file will indicate that the previous download was
+    # from the first class dep and the dir needs to be cleared.
     if os.path.exists(RUST_TOOLCHAIN_OUT_DIR):
-        if version == GetStampVersion():
+        if version == GetStampVersion() and not glob.glob(
+                os.path.join(RUST_TOOLCHAIN_OUT_DIR, '.*_is_first_class_gcs')):
             return 0
 
     if os.path.exists(RUST_TOOLCHAIN_OUT_DIR):
diff --git a/ui/android/java/res/values/color_palette.xml b/ui/android/java/res/values/color_palette.xml
index d571a54..cb6f4c0 100644
--- a/ui/android/java/res/values/color_palette.xml
+++ b/ui/android/java/res/values/color_palette.xml
@@ -36,7 +36,6 @@
     <color name="baseline_neutral_90_alpha_10">#19E3E3E3</color>
     <color name="baseline_neutral_90_alpha_12">#1EE3E3E3</color>
     <color name="baseline_neutral_90_alpha_38">#61E3E3E3</color>
-    <color name="baseline_neutral_90_alpha_55">#8CE3E3E3</color>
     <color name="baseline_neutral_80">#C7C7C7</color>
     <color name="baseline_neutral_70">#ABABAB</color>
     <color name="baseline_neutral_70_alpha_38">#61ABABAB</color>
diff --git a/ui/base/interaction/element_tracker_mac.mm b/ui/base/interaction/element_tracker_mac.mm
index 20c7f84..c5048910 100644
--- a/ui/base/interaction/element_tracker_mac.mm
+++ b/ui/base/interaction/element_tracker_mac.mm
@@ -71,7 +71,7 @@
     if (it == elements_.end()) {
       it = recycle_bin_.find(identifier);
       if (it == recycle_bin_.end()) {
-        NOTREACHED()
+        DUMP_WILL_BE_NOTREACHED_NORETURN()
             << "Element " << identifier
             << " had its activation sent after its menu was destroyed. This "
                "may be due to a race condition with renderer context menus; "
diff --git a/ui/chromeos/strings/network/network_element_localized_strings_provider.cc b/ui/chromeos/strings/network/network_element_localized_strings_provider.cc
index 6919b076..daf1191 100644
--- a/ui/chromeos/strings/network/network_element_localized_strings_provider.cc
+++ b/ui/chromeos/strings/network/network_element_localized_strings_provider.cc
@@ -340,6 +340,8 @@
       {"customApnLimitReached", IDS_SETTINGS_CUSTOM_APN_LIMIT_REACHED},
       {"apnSettingsZeroStateDescription",
        IDS_SETTINGS_APN_ZERO_STATE_DESCRIPTION},
+      {"apnSettingsZeroStateDescriptionWithAddLink",
+       IDS_SETTINGS_APN_ZERO_STATE_DESCRIPTION_WITH_ADD_LINK},
       {"apnSettingsDatabaseApnsErrorMessage",
        IDS_SETTINGS_APN_DATABASE_APNS_ERROR_MESSAGE},
       {"apnSettingsCustomApnsErrorMessage",
@@ -554,6 +556,12 @@
                          l10n_util::GetStringFUTF16(
                              IDS_SETTINGS_APN_DESCRIPTION_WITH_LEARN_MORE_LINK,
                              chrome::kApnSettingsLearnMoreUrl));
+
+  html_source->AddString(
+      "apnSelectionDialogDescriptionWithLink",
+      l10n_util::GetStringFUTF16(
+          IDS_SETTINGS_APN_SELECTION_DIALOG_DESCRIPTION_WITH_LINK,
+          chrome::kApnSettingsLearnMoreUrl));
 }
 
 void AddConfigLocalizedStrings(content::WebUIDataSource* html_source) {
diff --git a/ui/gfx/linux/drm_util_linux.cc b/ui/gfx/linux/drm_util_linux.cc
index 6714f488..d0b8769 100644
--- a/ui/gfx/linux/drm_util_linux.cc
+++ b/ui/gfx/linux/drm_util_linux.cc
@@ -6,6 +6,7 @@
 
 #include <drm_fourcc.h>
 
+#include "base/logging.h"
 #include "base/notreached.h"
 
 namespace ui {
@@ -105,4 +106,27 @@
   }
 }
 
+const char* DrmFormatToString(uint32_t format) {
+#define STRINGIFY(V) \
+  case V:            \
+    return #V
+  switch (format) {
+    STRINGIFY(DRM_FORMAT_R8);
+    STRINGIFY(DRM_FORMAT_GR88);
+    STRINGIFY(DRM_FORMAT_ABGR8888);
+    STRINGIFY(DRM_FORMAT_XBGR8888);
+    STRINGIFY(DRM_FORMAT_ARGB8888);
+    STRINGIFY(DRM_FORMAT_XRGB8888);
+    STRINGIFY(DRM_FORMAT_ARGB2101010);
+    STRINGIFY(DRM_FORMAT_ABGR2101010);
+    STRINGIFY(DRM_FORMAT_RGB565);
+    STRINGIFY(DRM_FORMAT_YVU420);
+    STRINGIFY(DRM_FORMAT_P010);
+    STRINGIFY(DRM_FORMAT_ABGR16161616F);
+    case DRM_FORMAT_INVALID:  // fallthroughs
+    default:
+      return "DRM_FORMAT_INVALID";
+  }
+#undef STRINGIFY
+}
 }  // namespace ui
diff --git a/ui/gfx/linux/drm_util_linux.h b/ui/gfx/linux/drm_util_linux.h
index 2db49493..4d4d1b5 100644
--- a/ui/gfx/linux/drm_util_linux.h
+++ b/ui/gfx/linux/drm_util_linux.h
@@ -17,6 +17,10 @@
 // Returns true if the fourcc format is known.
 bool IsValidBufferFormat(uint32_t current_format);
 
+// Returns a human-readable string for a DRM FourCC format, or
+// DRM_FORMAT_INVALID for an unknown or unsupported DRM format.
+const char* DrmFormatToString(uint32_t format);
+
 }  // namespace ui
 
 #endif  // UI_GFX_LINUX_DRM_UTIL_LINUX_H__
diff --git a/ui/gfx/linux/gbm_wrapper.cc b/ui/gfx/linux/gbm_wrapper.cc
index 3df651a..8d60e2c 100644
--- a/ui/gfx/linux/gbm_wrapper.cc
+++ b/ui/gfx/linux/gbm_wrapper.cc
@@ -400,7 +400,7 @@
 
     int gbm_flags = 0;
     if ((gbm_flags = GetSupportedGbmFlags(format)) == 0) {
-      LOG(ERROR) << "gbm format not supported: " << format;
+      LOG(ERROR) << "gbm format not supported: " << DrmFormatToString(format);
       return nullptr;
     }
 
diff --git a/ui/gl/gl_bindings_autogen_gl.cc b/ui/gl/gl_bindings_autogen_gl.cc
index bac9e036..8c27e94 100644
--- a/ui/gl/gl_bindings_autogen_gl.cc
+++ b/ui/gl/gl_bindings_autogen_gl.cc
@@ -15941,8 +15941,8 @@
 
 namespace {
 void NoContextHelper(const char* method_name) {
-  NOTREACHED() << "Trying to call " << method_name
-               << " without current GL context";
+  DUMP_WILL_BE_NOTREACHED_NORETURN()
+      << "Trying to call " << method_name << " without current GL context";
   LOG(ERROR) << "Trying to call " << method_name
              << " without current GL context";
 }
diff --git a/ui/ozone/platform/drm/gpu/drm_framebuffer.cc b/ui/ozone/platform/drm/gpu/drm_framebuffer.cc
index 2eb5a00..47327ee 100644
--- a/ui/ozone/platform/drm/gpu/drm_framebuffer.cc
+++ b/ui/ozone/platform/drm/gpu/drm_framebuffer.cc
@@ -68,7 +68,7 @@
                                    params.offsets, modifiers, &framebuffer_id,
                                    params.flags)) {
     VLOG(4) << "AddFramebuffer2:" << "size=" << params.width << "x"
-            << params.height << " drm_format=" << (int)drm_format
+            << params.height << " drm_format=" << DrmFormatToString(drm_format)
             << " fb_id=" << framebuffer_id << " flags=" << params.flags;
     return nullptr;
   }
@@ -80,7 +80,7 @@
                                    params.offsets, modifiers,
                                    &opaque_framebuffer_id, params.flags)) {
     VLOG(4) << "AddFramebuffer2:" << "size=" << params.width << "x"
-            << params.height << " drm_format=" << (int)drm_format
+            << params.height << " drm_format=" << DrmFormatToString(drm_format)
             << " fb_id=" << opaque_framebuffer_id << " flags=" << params.flags;
     drm_device->RemoveFramebuffer(framebuffer_id);
     return nullptr;
diff --git a/ui/ozone/platform/drm/gpu/drm_gpu_display_manager_unittest.cc b/ui/ozone/platform/drm/gpu/drm_gpu_display_manager_unittest.cc
index 685a0243..a1b643d 100644
--- a/ui/ozone/platform/drm/gpu/drm_gpu_display_manager_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/drm_gpu_display_manager_unittest.cc
@@ -160,14 +160,17 @@
   void TearDown() override {
     drm_gpu_display_manager_ = nullptr;
     screen_manager_ = nullptr;
+    for (auto& device : device_manager_->GetDrmDevices()) {
+      FakeDrmDevice* fake_drm = static_cast<FakeDrmDevice*>(device.get());
+      fake_drm->ResetPlaneManagerForTesting();
+    }
     device_manager_ = nullptr;
     next_drm_device_number_ = 0u;
   }
 
   // Note: the first device added will be marked as the primary device.
   scoped_refptr<FakeDrmDevice> AddAndInitializeDrmDeviceWithState(
-      FakeDrmDevice::MockDrmState& drm_state,
-      bool use_atomic = true) {
+      FakeDrmDevice::MockDrmState& drm_state) {
     std::string card_path = base::StringPrintf(kDefaultTestGraphicsCardPattern,
                                                next_drm_device_number_++);
     base::FilePath file_path(card_path);
@@ -177,7 +180,7 @@
         device_manager_->GetDrmDevices().back().get());
     fake_drm->SetPropertyBlob(FakeDrmDevice::AllocateInFormatsBlob(
         kInFormatsBlobIdBase, {DRM_FORMAT_XRGB8888}, {}));
-    fake_drm->InitializeState(drm_state, use_atomic);
+    fake_drm->InitializeState(drm_state, /* use_atomic */ true);
     return scoped_refptr<FakeDrmDevice>(fake_drm);
   }
 
@@ -261,16 +264,7 @@
   ASSERT_EQ(drm_gpu_display_manager_->GetDisplays().size(), kMaxDrmConnectors);
 }
 
-// TODO(crbug.com/1431767): Re-enable this test
-#if defined(LEAK_SANITIZER)
-#define MAYBE_FindAndConfigureDisplaysOnSameDrmDevice \
-  DISABLED_FindAndConfigureDisplaysOnSameDrmDevice
-#else
-#define MAYBE_FindAndConfigureDisplaysOnSameDrmDevice \
-  FindAndConfigureDisplaysOnSameDrmDevice
-#endif
-TEST_F(DrmGpuDisplayManagerTest,
-       MAYBE_FindAndConfigureDisplaysOnSameDrmDevice) {
+TEST_F(DrmGpuDisplayManagerTest, FindAndConfigureDisplaysOnSameDrmDevice) {
   // One DRM device.
   auto drm_state = FakeDrmDevice::MockDrmState::CreateStateWithAllProperties();
 
@@ -318,16 +312,8 @@
 
 // This case tests scenarios in which a display ID is searched across multiple
 // DRM devices, such as in DisplayLink hubs.
-// TODO(crbug.com/1431767): Re-enable this test
-#if defined(LEAK_SANITIZER)
-#define MAYBE_FindAndConfigureDisplaysAcrossDifferentDrmDevices \
-  DISABLED_FindAndConfigureDisplaysAcrossDifferentDrmDevices
-#else
-#define MAYBE_FindAndConfigureDisplaysAcrossDifferentDrmDevices \
-  FindAndConfigureDisplaysAcrossDifferentDrmDevices
-#endif
 TEST_F(DrmGpuDisplayManagerTest,
-       MAYBE_FindAndConfigureDisplaysAcrossDifferentDrmDevices) {
+       FindAndConfigureDisplaysAcrossDifferentDrmDevices) {
   // Add 3 DRM devices, each with one active display.
   for (size_t i = 0; i < 3; ++i) {
     auto drm_state =
@@ -374,16 +360,8 @@
                                  display::ModesetFlag::kCommitModeset}));
 }
 
-// TODO(crbug.com/1431767): Re-enable this test
-#if defined(LEAK_SANITIZER)
-#define MAYBE_OriginsPersistThroughSimilarExtendedModeConfigurations \
-  DISABLED_OriginsPersistThroughSimilarExtendedModeConfigurations
-#else
-#define MAYBE_OriginsPersistThroughSimilarExtendedModeConfigurations \
-  OriginsPersistThroughSimilarExtendedModeConfigurations
-#endif
 TEST_F(DrmGpuDisplayManagerTest,
-       MAYBE_OriginsPersistThroughSimilarExtendedModeConfigurations) {
+       OriginsPersistThroughSimilarExtendedModeConfigurations) {
   // One DRM device.
   auto drm_state = FakeDrmDevice::MockDrmState::CreateStateWithAllProperties();
 
@@ -655,11 +633,6 @@
                                 {display::ModesetFlag::kCommitModeset}));
   EXPECT_EQ(primary_display->crtc(), crtc_1);
   EXPECT_EQ(secondary_display->crtc(), crtc_3);
-
-  // DrmDevice seems to leak on successful configure in tests. Manually
-  // checking for mock calls and allowing leak for now.
-  ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(mock_drm));
-  testing::Mock::AllowLeak(mock_drm);
 }
 
 TEST_F(DrmGpuDisplayManagerMockedDeviceTest,
@@ -986,11 +959,6 @@
 
     EXPECT_TRUE(ConfigureDisplays(display_snapshots,
                                   {display::ModesetFlag::kCommitModeset}));
-
-    // DrmDevice seems to leak on successful configure in tests. Manually
-    // checking for mock calls and allowing leak for now.
-    ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(mock_drm));
-    testing::Mock::AllowLeak(mock_drm);
   }
 
   DrmDisplay* primary_display = FindDisplayByConnectorId(primary_connector_id);
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
index a74c097..39267ac 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
@@ -89,6 +89,7 @@
   }
 
   void SetUp() override;
+  void TearDown() override;
 
   scoped_refptr<DrmFramebuffer> CreateBuffer(const gfx::Size& size) {
     return CreateBufferWithFormat(size, DRM_FORMAT_XRGB8888);
@@ -120,6 +121,10 @@
   fake_buffer_ = CreateBuffer(kDefaultBufferSize);
 }
 
+void HardwareDisplayPlaneManagerTest::TearDown() {
+  fake_drm_->ResetPlaneManagerForTesting();
+}
+
 void HardwareDisplayPlaneManagerTest::PerformPageFlip(
     size_t crtc_idx,
     HardwareDisplayPlaneList* state) {
@@ -188,13 +193,7 @@
 using HardwareDisplayPlaneManagerLegacyTest = HardwareDisplayPlaneManagerTest;
 using HardwareDisplayPlaneManagerAtomicTest = HardwareDisplayPlaneManagerTest;
 
-// TODO(crbug.com/1431767): Re-enable this test
-#if defined(LEAK_SANITIZER)
-#define MAYBE_ResettingConnectorCache DISABLED_ResettingConnectorCache
-#else
-#define MAYBE_ResettingConnectorCache ResettingConnectorCache
-#endif
-TEST_P(HardwareDisplayPlaneManagerTest, MAYBE_ResettingConnectorCache) {
+TEST_P(HardwareDisplayPlaneManagerTest, ResettingConnectorCache) {
   const int connector_and_crtc_count = 3;
   auto drm_state = FakeDrmDevice::MockDrmState::CreateStateWithDefaultObjects(
       connector_and_crtc_count,
@@ -463,13 +462,7 @@
       &state_, assigns, fake_drm_->crtc_property(0).id));
 }
 
-// TODO(crbug.com/1431767): Re-enable this test
-#if defined(LEAK_SANITIZER)
-#define MAYBE_Modeset DISABLED_Modeset
-#else
-#define MAYBE_Modeset Modeset
-#endif
-TEST_P(HardwareDisplayPlaneManagerAtomicTest, MAYBE_Modeset) {
+TEST_P(HardwareDisplayPlaneManagerAtomicTest, Modeset) {
   auto drm_state = FakeDrmDevice::MockDrmState::CreateStateWithDefaultObjects(
       /*crtc_count=*/1, /*planes_per_crtc=*/1);
   fake_drm_->InitializeState(drm_state, /*use_atomic=*/true);
@@ -505,13 +498,7 @@
   EXPECT_EQ(1, fake_drm_->get_commit_count());
 }
 
-// TODO(crbug.com/1431767): Re-enable this test
-#if defined(LEAK_SANITIZER)
-#define MAYBE_CheckPropsAfterModeset DISABLED_CheckPropsAfterModeset
-#else
-#define MAYBE_CheckPropsAfterModeset CheckPropsAfterModeset
-#endif
-TEST_P(HardwareDisplayPlaneManagerAtomicTest, MAYBE_CheckPropsAfterModeset) {
+TEST_P(HardwareDisplayPlaneManagerAtomicTest, CheckPropsAfterModeset) {
   auto drm_state = FakeDrmDevice::MockDrmState::CreateStateWithDefaultObjects(
       /*crtc_count=*/1, /*planes_per_crtc=*/1);
   fake_drm_->InitializeState(drm_state, /*use_atomic=*/true);
@@ -548,13 +535,7 @@
   EXPECT_EQ(kModePropId, crtc_prop_for_name.id);
 }
 
-// TODO(crbug.com/1431767): Re-enable this test
-#if defined(LEAK_SANITIZER)
-#define MAYBE_CheckPropsAfterDisable DISABLED_CheckPropsAfterDisable
-#else
-#define MAYBE_CheckPropsAfterDisable CheckPropsAfterDisable
-#endif
-TEST_P(HardwareDisplayPlaneManagerAtomicTest, MAYBE_CheckPropsAfterDisable) {
+TEST_P(HardwareDisplayPlaneManagerAtomicTest, CheckPropsAfterDisable) {
   auto drm_state = FakeDrmDevice::MockDrmState::CreateStateWithDefaultObjects(
       /*crtc_count=*/1, /*planes_per_crtc=*/1);
   fake_drm_->InitializeState(drm_state, /*use_atomic=*/true);
@@ -591,13 +572,7 @@
   EXPECT_EQ(0U, crtc_prop_for_name.value);
 }
 
-// TODO(crbug.com/1431767): Re-enable this test
-#if defined(LEAK_SANITIZER)
-#define MAYBE_CheckVrrAfterModeset DISABLED_CheckVrrAfterModeset
-#else
-#define MAYBE_CheckVrrAfterModeset CheckVrrAfterModeset
-#endif
-TEST_P(HardwareDisplayPlaneManagerAtomicTest, MAYBE_CheckVrrAfterModeset) {
+TEST_P(HardwareDisplayPlaneManagerAtomicTest, CheckVrrAfterModeset) {
   auto drm_state = FakeDrmDevice::MockDrmState::CreateStateWithDefaultObjects(
       /*crtc_count=*/1, /*planes_per_crtc=*/2);
   drm_state.crtc_properties[0].properties.push_back(
diff --git a/ui/views/window/dialog_delegate.cc b/ui/views/window/dialog_delegate.cc
index 8bc19c1..1361ac9 100644
--- a/ui/views/window/dialog_delegate.cc
+++ b/ui/views/window/dialog_delegate.cc
@@ -17,6 +17,7 @@
 #include "ui/accessibility/ax_role_properties.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/ui_base_types.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/strings/grit/ui_strings.h"
 #include "ui/views/bubble/bubble_border.h"
@@ -35,6 +36,29 @@
 
 namespace {
 
+// Class that ensures that dialogs are logically "parented" to their parent for
+// various purposes, including testing, theming, and Tutorials.
+class DialogWidget : public Widget {
+ public:
+  DialogWidget() = default;
+  ~DialogWidget() override = default;
+
+  // Widget:
+  Widget* GetPrimaryWindowWidget() override {
+    // Dialogs are usually parented to another window, so that window should be
+    // the primary window. Only fall back to default Widget behavior if there is
+    // no parent.
+    return parent() ? parent()->GetPrimaryWindowWidget()
+                    : Widget::GetPrimaryWindowWidget();
+  }
+
+  // TODO(dfried): Possibly also fix the following (possibly in Widget) so they
+  // don't have to be overridden in bubble_dialog_delegate_view.cc:
+  //  - GetCustomTheme()
+  //  - GetNativeTheme()
+  //  - GetColorProvider()
+};
+
 bool HasCallback(
     const absl::variant<base::OnceClosure, base::RepeatingCallback<bool()>>&
         callback) {
@@ -61,7 +85,7 @@
 Widget* DialogDelegate::CreateDialogWidget(WidgetDelegate* delegate,
                                            gfx::NativeWindow context,
                                            gfx::NativeView parent) {
-  views::Widget* widget = new views::Widget;
+  views::Widget* widget = new DialogWidget;
   views::Widget::InitParams params =
       GetDialogWidgetInitParams(delegate, context, parent, gfx::Rect());
   widget->Init(std::move(params));
diff --git a/ui/webui/resources/cr_components/help_bubble/help_bubble.css b/ui/webui/resources/cr_components/help_bubble/help_bubble.css
index 12d70e1..523f5493 100644
--- a/ui/webui/resources/cr_components/help_bubble/help_bubble.css
+++ b/ui/webui/resources/cr_components/help_bubble/help_bubble.css
@@ -291,9 +291,9 @@
 }
 
 #buttons cr-button {
-  --border-color: var(--help-bubble-foreground);
-  --text-color: var(--help-bubble-button-foreground);
-  background-color: var(--help-bubble-button-background);
+  --cr-button-border-color: var(--help-bubble-foreground);
+  --cr-button-text-color: var(--help-bubble-button-foreground);
+  --cr-button-background-color: var(--help-bubble-button-background);
 }
 
 #buttons cr-button:focus {
diff --git a/ui/webui/resources/cr_components/history_embeddings/filter_chips.html b/ui/webui/resources/cr_components/history_embeddings/filter_chips.html
index ecb7779..ccdf550 100644
--- a/ui/webui/resources/cr_components/history_embeddings/filter_chips.html
+++ b/ui/webui/resources/cr_components/history_embeddings/filter_chips.html
@@ -18,6 +18,10 @@
   display: flex;
   gap: 8px;
 }
+
+.suggestion-label {
+  text-transform: lowercase;
+}
 </style>
 
 <cr-chip id="byGroupChip" selected$="[[showResultsByGroup]]"
@@ -34,7 +38,7 @@
   <template is="dom-repeat" items="[[suggestions_]]">
     <cr-chip on-click="onSuggestionClick_"
         selected$="[[isSuggestionSelected_(item, selectedSuggestion)]]">
-      [[item]]
+      <span class="suggestion-label">[[item.label]]</span>
     </cr-chip>
   </template>
 </div>
diff --git a/ui/webui/resources/cr_components/history_embeddings/filter_chips.ts b/ui/webui/resources/cr_components/history_embeddings/filter_chips.ts
index d3535d50..e0550422 100644
--- a/ui/webui/resources/cr_components/history_embeddings/filter_chips.ts
+++ b/ui/webui/resources/cr_components/history_embeddings/filter_chips.ts
@@ -15,6 +15,38 @@
 
 import {getTemplate} from './filter_chips.html.js';
 
+export interface Suggestion {
+  label: string;
+  timeRangeStart: Date;
+}
+
+function generateSuggestions(): Suggestion[] {
+  const yesterday = new Date();
+  yesterday.setDate(yesterday.getDate() - 1);
+  yesterday.setHours(0, 0, 0, 0);
+  const last7Days = new Date();
+  last7Days.setDate(last7Days.getDate() - 7);
+  last7Days.setHours(0, 0, 0, 0);
+  const last30Days = new Date();
+  last30Days.setDate(last30Days.getDate() - 30);
+  last30Days.setHours(0, 0, 0, 0);
+
+  return [
+    {
+      label: loadTimeData.getString('historyEmbeddingsSuggestion1'),
+      timeRangeStart: yesterday,
+    },
+    {
+      label: loadTimeData.getString('historyEmbeddingsSuggestion2'),
+      timeRangeStart: last7Days,
+    },
+    {
+      label: loadTimeData.getString('historyEmbeddingsSuggestion3'),
+      timeRangeStart: last30Days,
+    },
+  ];
+}
+
 export interface HistoryEmbeddingsFilterChips {
   $: {
     byGroupChip: HTMLElement,
@@ -43,29 +75,21 @@
       },
       suggestions_: {
         type: Array,
-        value: () => {
-          return [
-            loadTimeData.getString('historyEmbeddingsSuggestion1')
-                .toLowerCase(),
-            loadTimeData.getString('historyEmbeddingsSuggestion2')
-                .toLowerCase(),
-            loadTimeData.getString('historyEmbeddingsSuggestion3')
-                .toLowerCase(),
-          ];
-        },
+        value: () => generateSuggestions(),
       },
     };
   }
 
-  selectedSuggestion?: string;
+  selectedSuggestion?: Suggestion;
   showResultsByGroup: boolean;
-  private suggestions_: string[];
+  private suggestions_: Suggestion[];
+  timeRangeStart?: Date;
 
   private getByGroupIcon_(): string {
     return this.showResultsByGroup ? 'cr:check' : 'history-embeddings:by-group';
   }
 
-  private isSuggestionSelected_(suggestion: string): boolean {
+  private isSuggestionSelected_(suggestion: Suggestion): boolean {
     return this.selectedSuggestion === suggestion;
   }
 
@@ -73,7 +97,7 @@
     this.showResultsByGroup = !this.showResultsByGroup;
   }
 
-  private onSuggestionClick_(e: DomRepeatEvent<string>) {
+  private onSuggestionClick_(e: DomRepeatEvent<Suggestion>) {
     const clickedSuggestion = e.model.item;
     if (this.isSuggestionSelected_(clickedSuggestion)) {
       this.selectedSuggestion = undefined;
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 8452f559..0990310 100644
--- a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.ts
+++ b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.ts
@@ -9,6 +9,7 @@
 import '//resources/cr_elements/cr_url_list_item/cr_url_list_item.js';
 
 import {I18nMixin} from '//resources/cr_elements/i18n_mixin.js';
+import type {Time} from '//resources/mojo/mojo/public/mojom/base/time.mojom-webui.js';
 import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import type {DomRepeatEvent} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -16,6 +17,14 @@
 import {getTemplate} from './history_embeddings.html.js';
 import type {SearchQuery, SearchResult, SearchResultItem} from './history_embeddings.mojom-webui.js';
 
+function jsDateToMojoDate(date: Date): Time {
+  const windowsEpoch = Date.UTC(1601, 0, 1, 0, 0, 0, 0);
+  const unixEpoch = Date.UTC(1970, 0, 1, 0, 0, 0, 0);
+  const epochDeltaInMs = unixEpoch - windowsEpoch;
+  const internalValue = BigInt(date.valueOf() + epochDeltaInMs) * BigInt(1000);
+  return {internalValue};
+}
+
 export interface HistoryEmbeddingsElement {
   $: {
     heading: HTMLElement,
@@ -36,17 +45,22 @@
   static get properties() {
     return {
       loading_: Boolean,
-      searchQuery: {
-        type: String,
-        observer: 'onSearchQueryChanged_',
-      },
+      searchQuery: String,
+      timeRangeStart: Object,
     };
   }
 
+  static get observers() {
+    return [
+      'onSearchQueryChanged_(searchQuery, timeRangeStart)',
+    ];
+  }
+
   private browserProxy_ = HistoryEmbeddingsBrowserProxyImpl.getInstance();
   private loading_ = false;
   private searchResult_: SearchResult;
   searchQuery: string;
+  timeRangeStart?: Date;
 
   private getHeadingText_(): string {
     if (this.loading_) {
@@ -68,7 +82,8 @@
     this.loading_ = true;
     const query: SearchQuery = {
       query: this.searchQuery,
-      timeRangeStart: null,
+      timeRangeStart:
+          this.timeRangeStart ? jsDateToMojoDate(this.timeRangeStart) : null,
     };
     this.browserProxy_.search(query).then((result) => {
       this.searchResult_ = result;
diff --git a/ui/webui/resources/cr_elements/cr_button/cr_button.css b/ui/webui/resources/cr_elements/cr_button/cr_button.css
index 4e1b55f..69dc68d 100644
--- a/ui/webui/resources/cr_elements/cr_button/cr_button.css
+++ b/ui/webui/resources/cr_elements/cr_button/cr_button.css
@@ -11,244 +11,132 @@
  * #css_wrapper_metadata_end */
 
 :host {
-  --active-shadow-rgb: var(--google-grey-800-rgb);
-  --active-shadow-action-rgb: var(--google-blue-500-rgb);
-  --bg-action: var(--google-blue-600);
-  --border-color: var(--google-grey-300);
-  --disabled-bg-action: var(--google-grey-100);
-  --disabled-bg: white;
-  --disabled-border-color: var(--google-grey-100);
-  --disabled-text-color: var(--google-grey-600);
-  --focus-shadow-color: rgba(var(--google-blue-600-rgb), .4);
-  --hover-bg-action: rgba(var(--google-blue-600-rgb), .9);
-  --hover-bg-color: rgba(var(--google-blue-500-rgb), .04);
-  --hover-border-color: var(--google-blue-100);
-  --hover-shadow-action-rgb: var(--google-blue-500-rgb);
-  --ink-color-action: white;
-  /* Blue-ish color used either as a background or as a text color,
-   * depending on the type of button. */
-  --ink-color: var(--google-blue-600);
-  --ripple-opacity-action: .32;
-  --ripple-opacity: .1;
-  --text-color-action: white;
-  --text-color: var(--google-blue-600);
+  --cr-button-background-color: transparent;
+  --cr-button-border-color:  var(--color-button-border,
+      var(--cr-fallback-color-tonal-outline));
+  --cr-button-text-color: var(--color-button-foreground,
+      var(--cr-fallback-color-primary));
+  --cr-button-ripple-opacity: 1;
+  --cr-button-ripple-color: var(--cr-active-background-color);
+
+  --cr-button-disabled-background-color: transparent;
+  --cr-button-disabled-border-color: var(--color-button-border-disabled,
+      var(--cr-fallback-color-disabled-background));
+  --cr-button-disabled-text-color: var(--color-button-foreground-disabled,
+      var(--cr-fallback-color-disabled-foreground));
 }
 
-@media (prefers-color-scheme: dark) {
-  :host {
-    /* Only in dark. */
-    --active-bg: black linear-gradient(rgba(255, 255, 255, .06),
-                                       rgba(255, 255, 255, .06));
-    --active-shadow-rgb: 0, 0, 0;
-    --active-shadow-action-rgb: var(--google-blue-500-rgb);
-    --bg-action: var(--google-blue-300);
-    --border-color: var(--google-grey-700);
-    --disabled-bg-action: var(--google-grey-800);
-    /* TODO(dbeam): get --disabled-bg from Namrata. */
-    --disabled-bg: transparent;
-    --disabled-border-color: var(--google-grey-800);
-    --disabled-text-color: var(--google-grey-500);
-    --focus-shadow-color: rgba(var(--google-blue-300-rgb), .5);
-    --hover-bg-action: var(--bg-action)
-        linear-gradient(rgba(0, 0, 0, .08), rgba(0, 0, 0, .08));
-    --hover-bg-color: rgba(var(--google-blue-300-rgb), .08);
-    --ink-color-action: black;
-    --ink-color: var(--google-blue-300);
-    --ripple-opacity-action: .16;
-    --ripple-opacity: .16;
-    --text-color-action: var(--google-grey-900);
-    --text-color: var(--google-blue-300);
-  }
+:host(.action-button) {
+  --cr-button-background-color: var(--color-button-background-prominent,
+      var(--cr-fallback-color-primary));
+  --cr-button-text-color: var(--color-button-foreground-prominent,
+      var(--cr-fallback-color-on-primary));
+  --cr-button-ripple-color: var(--cr-active-on-primary-background-color);
+  --cr-button-border: none;
+
+  --cr-button-disabled-background-color: var(
+      --color-button-background-prominent-disabled,
+      var(--cr-fallback-color-disabled-background));
+  --cr-button-disabled-text-color: var(--color-button-foreground-disabled,
+      var(--cr-fallback-color-disabled-foreground));
+  --cr-button-disabled-border: none;
+}
+
+:host(.tonal-button),
+:host(.floating-button) {
+  --cr-button-background-color: var(--color-button-background-tonal,
+      var(--cr-fallback-color-secondary-container));
+  --cr-button-text-color: var(--color-button-foreground-tonal,
+      var(--cr-fallback-color-on-tonal-container));
+  --cr-button-border: none;
+
+  --cr-button-disabled-background-color: var(
+      --color-button-background-tonal-disabled,
+      var(--cr-fallback-color-disabled-background));
+  --cr-button-disabled-text-color: var(--color-button-foreground-disabled,
+      var(--cr-fallback-color-disabled-foreground));
+  --cr-button-disabled-border: none;
 }
 
 :host {
-  --paper-ripple-opacity: var(--ripple-opacity);
-  -webkit-tap-highlight-color: transparent;
-  align-items: center;
-  border: 1px solid var(--border-color);
-  border-radius: 4px;
-  box-sizing: border-box;
-  color: var(--text-color);
-  cursor: pointer;
-  display: inline-flex;
   flex-shrink: 0;
-  font-weight: 500;
-  height: var(--cr-button-height);
+  display: inline-flex;
+  align-items: center;
   justify-content: center;
+  box-sizing: border-box;
   min-width: 5.14em;
+  height: var(--cr-button-height);
+  padding: 8px 16px;
   outline-width: 0;
   overflow: hidden;
-  padding: 8px 16px;
   position: relative;
+  cursor: pointer;
   user-select: none;
+  -webkit-tap-highlight-color: transparent;
+  border: var(--cr-button-border, 1px solid var(--cr-button-border-color));
+  border-radius: 100px;
+  background: var(--cr-button-background-color);
+  color: var(--cr-button-text-color);
+  font-weight: 500;
+  line-height: 20px;
+  isolation: isolate;
 }
 
-:host-context([chrome-refresh-2023]):host {
-  /* Default button colors. */
-  --border-color: var(--color-button-border,
-      var(--cr-fallback-color-tonal-outline));
-  --text-color: var(--color-button-foreground,
-      var(--cr-fallback-color-primary));
-  --hover-bg-color: transparent;
-  --hover-border-color: var(--border-color);
-  --active-bg: transparent;
-  --active-shadow: none;
-  --ink-color: var(--cr-active-background-color);
-  --ripple-opacity: 1;
+@media (forced-colors: active) {
+  :host {
+    /* Color pipeline colors automatically provide high-contrast colors in
+    * high-contrast mode, and fallbackvars have high-contrast fallbacks
+    * for critically important states such as disabled state. */
+    forced-color-adjust: none;
+  }
+}
 
-  /* Disabled default button colors. */
-  --disabled-bg: transparent;
-  --disabled-border-color: var(--color-button-border-disabled,
-      var(--cr-fallback-color-disabled-background));
-  --disabled-text-color: var(--color-button-foreground-disabled,
-      var(--cr-fallback-color-disabled-foreground));
+:host(.floating-button) {
+  border-radius: 8px;
+  height: 40px;
+  transition: box-shadow 80ms linear;
+}
 
-  /* Action button colors. */
-  --bg-action: var(--color-button-background-prominent,
-      var(--cr-fallback-color-primary));
-  --text-color-action: var(--color-button-foreground-prominent,
-      var(--cr-fallback-color-on-primary));
-  --hover-bg-action: var(--bg-action);
-  --active-shadow-action: none;
-  --ink-color-action: var(--cr-active-background-color);
-  --ripple-opacity-action: 1;
-
-  /* Disabled action button colors. */
-  --disabled-bg-action: var(--color-button-background-prominent-disabled,
-      var(--cr-fallback-color-disabled-background));
-
-  background: transparent;
-  border-radius: 100px;
-  isolation: isolate;
-  line-height: 20px;
+:host(.floating-button:hover) {
+  box-shadow: var(--cr-elevation-3);
 }
 
 :host([has-prefix-icon_]),
 :host([has-suffix-icon_]) {
-  --iron-icon-height: 16px;
-  --iron-icon-width: 16px;
-  gap: 8px;
-  padding: 8px;
-}
-
-:host-context([chrome-refresh-2023]):host([has-prefix-icon_]),
-:host-context([chrome-refresh-2023]):host([has-suffix-icon_]) {
   --iron-icon-height: 20px;
   --iron-icon-width: 20px;
   --icon-block-padding-large: 16px;
   --icon-block-padding-small: 12px;
+  gap: 8px;
   padding-block-end: 8px;
   padding-block-start: 8px;
 }
 
-:host-context([chrome-refresh-2023]):host([has-prefix-icon_]) {
+:host([has-prefix-icon_]) {
   padding-inline-end: var(--icon-block-padding-large);
   padding-inline-start: var(--icon-block-padding-small);
 }
 
-:host-context([chrome-refresh-2023]):host([has-suffix-icon_]) {
+:host([has-suffix-icon_]) {
   padding-inline-end: var(--icon-block-padding-small);
   padding-inline-start: var(--icon-block-padding-large);
 }
 
 :host-context(.focus-outline-visible):host(:focus) {
-  box-shadow: 0 0 0 2px var(--focus-shadow-color);
-}
-
-@media (forced-colors: active) {
-  :host-context(.focus-outline-visible):host(:focus) {
-    /* Use outline instead of box-shadow (which does not work) in Windows
-       HCM. */
-    outline: var(--cr-focus-outline-hcm);
-  }
-
-  :host-context([chrome-refresh-2023]):host {
-    /* Refresh styles either use color pipeline colors which automatically
-     * provide high-contrast colors in high-contrast mode, or use fallback
-     * vars that have high-contrast fallbacks for critically important
-     * states such as disabled state. */
-    forced-color-adjust: none;
-  }
-}
-
-:host-context([chrome-refresh-2023].focus-outline-visible):host(:focus) {
   box-shadow: none;
   outline: 2px solid var(--cr-focus-outline-color);
   outline-offset: 2px;
 }
 
-:host(:active) {
-  background: var(--active-bg);
-  box-shadow: var(--active-shadow,
-      0 1px 2px 0 rgba(var(--active-shadow-rgb), .3),
-      0 3px 6px 2px rgba(var(--active-shadow-rgb), .15));
-}
-
-:host(:hover) {
-  background-color: var(--hover-bg-color);
-}
-
-@media (prefers-color-scheme: light) {
-  :host(:hover) {
-    border-color: var(--hover-border-color);
-  }
-}
-
 #background {
   border-radius: inherit;
   inset: 0;
   pointer-events: none;
   position: absolute;
-  z-index: 0;
-}
-
-:host-context([chrome-refresh-2023]):host(:hover) #background {
-  background-color: var(--hover-bg-color);
-}
-
-:host-context([chrome-refresh-2023].focus-outline-visible):host(:focus)
-    #background {
-  background-clip: padding-box;
-}
-
-:host-context([chrome-refresh-2023]):host(.action-button) #background {
-  background-color: var(--bg-action);
-}
-
-:host-context([chrome-refresh-2023]):host([disabled]) #background {
-  background-color: var(--disabled-bg);
-}
-
-:host-context([chrome-refresh-2023]):host(.action-button[disabled])
-    #background {
-  background-color: var(--disabled-bg-action);
-}
-
-:host-context([chrome-refresh-2023]):host(.tonal-button) #background,
-:host-context([chrome-refresh-2023]):host(.floating-button) #background {
-  background-color: var(--color-button-background-tonal,
-      var(--cr-fallback-color-secondary-container));
-}
-
-:host-context([chrome-refresh-2023]):host([disabled].tonal-button)
-    #background,
-:host-context([chrome-refresh-2023]):host([disabled].floating-button)
-    #background {
-  background-color: var(--color-button-background-tonal-disabled,
-      var(--cr-fallback-color-disabled-background));
 }
 
 #content {
-  display: contents;
-}
-
-:host-context([chrome-refresh-2023]) #content {
   display: inline;
-  z-index: 2;
-}
-
-:host-context([chrome-refresh-2023]) ::slotted(*) {
-  z-index: 2;
 }
 
 #hoverBackground {
@@ -260,65 +148,24 @@
   z-index: 1;
 }
 
-:host-context([chrome-refresh-2023]):host(:hover) #hoverBackground {
+:host(:hover) #hoverBackground {
   background: var(--cr-hover-background-color);
   display: block;
 }
 
-:host-context([chrome-refresh-2023]):host(.action-button:hover)
-    #hoverBackground {
+:host(.action-button:hover) #hoverBackground {
   background: var(--cr-hover-on-prominent-background-color);
 }
 
-:host(.action-button) {
-  --ink-color: var(--ink-color-action);
-  --paper-ripple-opacity: var(--ripple-opacity-action);
-  background-color: var(--bg-action);
-  border: none;
-  color: var(--text-color-action);
-}
-
-:host-context([chrome-refresh-2023]):host(.action-button) {
-  --ink-color: var(--cr-active-on-primary-background-color);
-  background-color: transparent;
-}
-
-:host(.action-button:active) {
-  box-shadow: var(--active-shadow-action,
-      0 1px 2px 0 rgba(var(--active-shadow-action-rgb), .3),
-      0 3px 6px 2px rgba(var(--active-shadow-action-rgb), .15));
-}
-
-:host(.action-button:hover) {
-  background: var(--hover-bg-action);
-}
-
-@media (prefers-color-scheme: light) {
-  :host(.action-button:not(:active):hover) {
-    box-shadow:
-        0 1px 2px 0 rgba(var(--hover-shadow-action-rgb), .3),
-        0 1px 3px 1px rgba(var(--hover-shadow-action-rgb), .15);
-  }
-
-  :host-context([chrome-refresh-2023]):host(
-      .action-button:not(:active):hover) {
-    box-shadow: none;
-  }
-}
-
 :host([disabled]) {
-  background-color: var(--disabled-bg);
-  border-color: var(--disabled-border-color);
-  color: var(--disabled-text-color);
+  background: var(--cr-button-disabled-background-color);
+  border: var(--cr-button-disabled-border,
+      1px solid var(--cr-button-disabled-border-color));
+  color: var(--cr-button-disabled-text-color);
   cursor: auto;
   pointer-events: none;
 }
 
-:host(.action-button[disabled]) {
-  background-color: var(--disabled-bg-action);
-  border-color: transparent;
-}
-
 /* cancel-button is meant to be used within a cr-dialog */
 :host(.cancel-button) {
   margin-inline-end: 8px;
@@ -329,33 +176,14 @@
   line-height: 154%;
 }
 
-:host-context([chrome-refresh-2023]):host(.tonal-button),
-:host-context([chrome-refresh-2023]):host(.floating-button) {
-  border: none;
-  color: var(--color-button-foreground-tonal,
-      var(--cr-fallback-color-on-tonal-container));
-}
-
-:host-context([chrome-refresh-2023]):host(.tonal-button[disabled]),
-:host-context([chrome-refresh-2023]):host(.floating-button[disabled]) {
-  border: none;
-  color: var(--disabled-text-color);
-}
-
-:host-context([chrome-refresh-2023]):host(.floating-button) {
-  border-radius: 8px;
-  height: 40px;
-  transition: box-shadow 80ms linear;
-}
-
-:host-context([chrome-refresh-2023]):host(.floating-button:hover) {
-  box-shadow: var(--cr-elevation-3);
-}
-
 #ink {
-  color: var(--ink-color);
+  color: var(--cr-button-ripple-color);
+  --paper-ripple-opacity: var(--cr-button-ripple-opacity);
 }
 
-:host-context([chrome-refresh-2023]) #ink {
-  z-index: 1;
-}
+/* Layering */
+#background { z-index: 0; }
+#hoverBackground,
+cr-ripple { z-index: 1; }
+#content,
+::slotted(*) { z-index: 2; }
diff --git a/ui/webui/resources/tools/codemods/lit_migration.py b/ui/webui/resources/tools/codemods/lit_migration.py
index f748b72..49337101 100755
--- a/ui/webui/resources/tools/codemods/lit_migration.py
+++ b/ui/webui/resources/tools/codemods/lit_migration.py
@@ -14,6 +14,24 @@
 sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
 import node
 
+"""
+ Instructions to run this script locally.
+ 1) Create a package.json file in the same folder with the following contents.
+
+{
+  "dependencies": {
+    "jscodeshift": "^0.15.1"
+  }
+}
+
+ 2) Run 'npm install' in the same folder.
+
+ 3) Invoke the script from the root directory of the repository. For example
+
+    python3 ui/webui/resources/tools/codemods/lit_migration.py \
+        --file ui/webui/resources/cr_components/most_visited/most_visited.ts
+"""
+
 
 def main(argv):
   parser = argparse.ArgumentParser()
diff --git a/url/android/gurl_android.cc b/url/android/gurl_android.cc
index 8fe192e1..4acf6060 100644
--- a/url/android/gurl_android.cc
+++ b/url/android/gurl_android.cc
@@ -46,16 +46,6 @@
 
 namespace {
 
-static std::unique_ptr<GURL> FromJavaGURL(const std::string& spec,
-                                          bool is_valid,
-                                          jlong parsed_ptr) {
-  Parsed* parsed = reinterpret_cast<Parsed*>(parsed_ptr);
-  std::unique_ptr<GURL> gurl =
-      std::make_unique<GURL>(spec.data(), parsed->Length(), *parsed, is_valid);
-  delete parsed;
-  return gurl;
-}
-
 static void InitFromGURL(JNIEnv* env,
                          const GURL& gurl,
                          const JavaRef<jobject>& target) {
@@ -85,10 +75,15 @@
 std::unique_ptr<GURL> GURLAndroid::ToNativeGURL(
     JNIEnv* env,
     const base::android::JavaRef<jobject>& j_gurl) {
-  return base::WrapUnique<GURL>(
-      reinterpret_cast<GURL*>(Java_GURL_toNativeGURL(env, j_gurl)));
+  GURL ret;
+  Parsed parsed;
+  Java_GURL_toNativeGURL(env, j_gurl, reinterpret_cast<jlong>(&ret),
+                         reinterpret_cast<jlong>(&parsed));
+  // TODO(agrieve): Return GURL rather than unique_ptr<GURL>.
+  return std::make_unique<GURL>(ret);
 }
 
+// TODO(agrieve): Replace with @JniType("std::vector<GURL>")
 void GURLAndroid::JavaGURLArrayToGURLVector(
     JNIEnv* env,
     const base::android::JavaRef<jobjectArray>& array,
@@ -101,8 +96,7 @@
   for (size_t i = 0; i < len; ++i) {
     ScopedJavaLocalRef<jobject> j_gurl(
         env, static_cast<jobject>(env->GetObjectArrayElement(array.obj(), i)));
-    out->emplace_back(
-        *reinterpret_cast<GURL*>(Java_GURL_toNativeGURL(env, j_gurl)));
+    out->push_back(*ToNativeGURL(env, j_gurl));
   }
 }
 
@@ -135,21 +129,15 @@
 }
 
 static void JNI_GURL_GetOrigin(JNIEnv* env,
-                               std::string& spec,
-                               jboolean is_valid,
-                               jlong parsed_ptr,
+                               GURL& gurl,
                                const JavaParamRef<jobject>& target) {
-  std::unique_ptr<GURL> gurl = FromJavaGURL(spec, is_valid, parsed_ptr);
-  InitFromGURL(env, gurl->DeprecatedGetOriginAsURL(), target);
+  InitFromGURL(env, gurl.DeprecatedGetOriginAsURL(), target);
 }
 
 static jboolean JNI_GURL_DomainIs(JNIEnv* env,
-                                  std::string& spec,
-                                  jboolean is_valid,
-                                  jlong parsed_ptr,
+                                  GURL& gurl,
                                   std::string& domain) {
-  std::unique_ptr<GURL> gurl = FromJavaGURL(spec, is_valid, parsed_ptr);
-  return gurl->DomainIs(domain);
+  return gurl.DomainIs(domain);
 }
 
 static void JNI_GURL_Init(JNIEnv* env,
@@ -159,19 +147,19 @@
   InitFromGURL(env, gurl, target);
 }
 
-static jlong JNI_GURL_CreateNative(JNIEnv* env,
-                                   std::string& spec,
-                                   jboolean is_valid,
-                                   jlong parsed_ptr) {
-  return reinterpret_cast<intptr_t>(
-      FromJavaGURL(spec, is_valid, parsed_ptr).release());
+static void JNI_GURL_InitNative(JNIEnv* env,
+                                std::string& spec,
+                                jboolean is_valid,
+                                jlong native_gurl,
+                                jlong native_parsed) {
+  GURL* gurl = reinterpret_cast<GURL*>(native_gurl);
+  Parsed* parsed = reinterpret_cast<Parsed*>(native_parsed);
+  *gurl = GURL(spec, *parsed, is_valid);
 }
 
 static void JNI_GURL_ReplaceComponents(
     JNIEnv* env,
-    std::string& spec,
-    jboolean is_valid,
-    jlong parsed_ptr,
+    GURL& gurl,
     const JavaParamRef<jstring>& j_username_replacement,
     jboolean clear_username,
     const JavaParamRef<jstring>& j_password_replacement,
@@ -199,8 +187,7 @@
     replacements.SetPasswordStr(password);
   }
 
-  std::unique_ptr<GURL> original = FromJavaGURL(spec, is_valid, parsed_ptr);
-  InitFromGURL(env, original->ReplaceComponents(replacements), j_result);
+  InitFromGURL(env, gurl.ReplaceComponents(replacements), j_result);
 }
 
 }  // namespace url
diff --git a/url/android/java/src/org/chromium/url/GURL.java b/url/android/java/src/org/chromium/url/GURL.java
index 3e92942..e053d613 100644
--- a/url/android/java/src/org/chromium/url/GURL.java
+++ b/url/android/java/src/org/chromium/url/GURL.java
@@ -146,8 +146,9 @@
     }
 
     @CalledByNative
-    private long toNativeGURL() {
-        return getNatives().createNative(mSpec, mIsValid, mParsed.toNativeParsed());
+    private void toNativeGURL(long nativeGurl, long nativeParsed) {
+        mParsed.initNative(nativeParsed);
+        GURLJni.get().initNative(mSpec, mIsValid, nativeGurl, nativeParsed);
     }
 
     /** See native GURL::is_valid(). */
@@ -239,12 +240,12 @@
     }
 
     protected void getOriginInternal(GURL target) {
-        getNatives().getOrigin(mSpec, mIsValid, mParsed.toNativeParsed(), target);
+        getNatives().getOrigin(this, target);
     }
 
     /** See native GURL::DomainIs(). */
     public boolean domainIs(String domain) {
-        return getNatives().domainIs(mSpec, mIsValid, mParsed.toNativeParsed(), domain);
+        return getNatives().domainIs(this, domain);
     }
 
     /**
@@ -265,15 +266,7 @@
             String username, boolean clearUsername, String password, boolean clearPassword) {
         GURL result = new GURL();
         getNatives()
-                .replaceComponents(
-                        mSpec,
-                        mIsValid,
-                        mParsed.toNativeParsed(),
-                        username,
-                        clearUsername,
-                        password,
-                        clearPassword,
-                        result);
+                .replaceComponents(this, username, clearUsername, password, clearPassword, result);
         return result;
     }
 
@@ -404,30 +397,24 @@
         /**
          * Reconstructs the native GURL for this Java GURL and initializes |target| with its Origin.
          */
-        void getOrigin(
-                @JniType("std::string") String spec,
-                boolean isValid,
-                long nativeParsed,
-                GURL target);
+        void getOrigin(@JniType("GURL") GURL self, GURL target);
 
         /** Reconstructs the native GURL for this Java GURL, and calls GURL.DomainIs. */
-        boolean domainIs(
+        boolean domainIs(@JniType("GURL") GURL self, @JniType("std::string") String domain);
+
+        /** Reconstructs the native GURL for this Java GURL, assigning it to nativeGurl. */
+        void initNative(
                 @JniType("std::string") String spec,
                 boolean isValid,
-                long nativeParsed,
-                @JniType("std::string") String domain);
-
-        /** Reconstructs the native GURL for this Java GURL, returning its native pointer. */
-        long createNative(@JniType("std::string") String spec, boolean isValid, long nativeParsed);
+                long nativeGurl,
+                long nativeParsed);
 
         /**
          * Reconstructs the native GURL for this Java GURL and initializes |result| with the result
          * of ReplaceComponents.
          */
         void replaceComponents(
-                @JniType("std::string") String spec,
-                boolean isValid,
-                long nativeParsed,
+                @JniType("GURL") GURL self,
                 String username,
                 boolean clearUsername,
                 String password,
diff --git a/url/android/java/src/org/chromium/url/Parsed.java b/url/android/java/src/org/chromium/url/Parsed.java
index 6ece743..087f3f6 100644
--- a/url/android/java/src/org/chromium/url/Parsed.java
+++ b/url/android/java/src/org/chromium/url/Parsed.java
@@ -74,31 +74,39 @@
         mInnerUrl = innerUrl;
     }
 
-    /* package */ long toNativeParsed() {
-        long inner = 0;
-        if (mInnerUrl != null) {
-            inner = mInnerUrl.toNativeParsed();
+    /* package */ void initNative(long nativePtr) {
+        Parsed target = this;
+        Parsed innerParsed = mInnerUrl;
+        // Use a loop to avoid two copies of the long parameter list.
+        while (true) {
+            // Send the outer Parsed first, and then mInnerUrl.
+            boolean isInner = target == innerParsed;
+            ParsedJni.get()
+                    .initNative(
+                            nativePtr,
+                            isInner,
+                            target.mSchemeBegin,
+                            target.mSchemeLength,
+                            target.mUsernameBegin,
+                            target.mUsernameLength,
+                            target.mPasswordBegin,
+                            target.mPasswordLength,
+                            target.mHostBegin,
+                            target.mHostLength,
+                            target.mPortBegin,
+                            target.mPortLength,
+                            target.mPathBegin,
+                            target.mPathLength,
+                            target.mQueryBegin,
+                            target.mQueryLength,
+                            target.mRefBegin,
+                            target.mRefLength,
+                            target.mPotentiallyDanglingMarkup);
+            if (isInner || innerParsed == null) {
+                break;
+            }
+            target = mInnerUrl;
         }
-        return ParsedJni.get()
-                .createNative(
-                        mSchemeBegin,
-                        mSchemeLength,
-                        mUsernameBegin,
-                        mUsernameLength,
-                        mPasswordBegin,
-                        mPasswordLength,
-                        mHostBegin,
-                        mHostLength,
-                        mPortBegin,
-                        mPortLength,
-                        mPathBegin,
-                        mPathLength,
-                        mQueryBegin,
-                        mQueryLength,
-                        mRefBegin,
-                        mRefLength,
-                        mPotentiallyDanglingMarkup,
-                        inner);
     }
 
     /* package */ String serialize() {
@@ -172,8 +180,9 @@
 
     @NativeMethods
     interface Natives {
-        /** Create and return the pointer to a native Parsed. */
-        long createNative(
+        void initNative(
+                long parsed,
+                boolean setAsInner,
                 int schemeBegin,
                 int schemeLength,
                 int usernameBegin,
@@ -190,7 +199,6 @@
                 int queryLength,
                 int refBegin,
                 int refLength,
-                boolean potentiallyDanglingMarkup,
-                long innerUrl);
+                boolean potentiallyDanglingMarkup);
     }
 }
diff --git a/url/android/parsed_android.cc b/url/android/parsed_android.cc
index e3b0d24..75964ab 100644
--- a/url/android/parsed_android.cc
+++ b/url/android/parsed_android.cc
@@ -48,49 +48,50 @@
   return CreateJavaParsed(env, parsed, inner);
 }
 
-static jlong JNI_Parsed_CreateNative(JNIEnv* env,
-                                     jint scheme_begin,
-                                     jint scheme_length,
-                                     jint username_begin,
-                                     jint username_length,
-                                     jint password_begin,
-                                     jint password_length,
-                                     jint host_begin,
-                                     jint host_length,
-                                     jint port_begin,
-                                     jint port_length,
-                                     jint path_begin,
-                                     jint path_length,
-                                     jint query_begin,
-                                     jint query_length,
-                                     jint ref_begin,
-                                     jint ref_length,
-                                     jboolean potentially_dangling_markup,
-                                     jlong inner_parsed) {
-  Parsed* parsed = new Parsed();
-  parsed->scheme.begin = scheme_begin;
-  parsed->scheme.len = scheme_length;
-  parsed->username.begin = username_begin;
-  parsed->username.len = username_length;
-  parsed->password.begin = password_begin;
-  parsed->password.len = password_length;
-  parsed->host.begin = host_begin;
-  parsed->host.len = host_length;
-  parsed->port.begin = port_begin;
-  parsed->port.len = port_length;
-  parsed->path.begin = path_begin;
-  parsed->path.len = path_length;
-  parsed->query.begin = query_begin;
-  parsed->query.len = query_length;
-  parsed->ref.begin = ref_begin;
-  parsed->ref.len = ref_length;
-  parsed->potentially_dangling_markup = potentially_dangling_markup;
-  Parsed* inner = reinterpret_cast<Parsed*>(inner_parsed);
-  if (inner) {
-    parsed->set_inner_parsed(*inner);
-    delete inner;
+static void JNI_Parsed_InitNative(JNIEnv* env,
+                                  jlong native_ptr,
+                                  jboolean is_inner,
+                                  jint scheme_begin,
+                                  jint scheme_length,
+                                  jint username_begin,
+                                  jint username_length,
+                                  jint password_begin,
+                                  jint password_length,
+                                  jint host_begin,
+                                  jint host_length,
+                                  jint port_begin,
+                                  jint port_length,
+                                  jint path_begin,
+                                  jint path_length,
+                                  jint query_begin,
+                                  jint query_length,
+                                  jint ref_begin,
+                                  jint ref_length,
+                                  jboolean potentially_dangling_markup) {
+  Parsed inner_parsed;
+  Parsed* outer_parsed = reinterpret_cast<Parsed*>(native_ptr);
+  Parsed* target = is_inner ? &inner_parsed : outer_parsed;
+  target->scheme.begin = scheme_begin;
+  target->scheme.len = scheme_length;
+  target->username.begin = username_begin;
+  target->username.len = username_length;
+  target->password.begin = password_begin;
+  target->password.len = password_length;
+  target->host.begin = host_begin;
+  target->host.len = host_length;
+  target->port.begin = port_begin;
+  target->port.len = port_length;
+  target->path.begin = path_begin;
+  target->path.len = path_length;
+  target->query.begin = query_begin;
+  target->query.len = query_length;
+  target->ref.begin = ref_begin;
+  target->ref.len = ref_length;
+  target->potentially_dangling_markup = potentially_dangling_markup;
+
+  if (is_inner) {
+    outer_parsed->set_inner_parsed(inner_parsed);
   }
-  return reinterpret_cast<intptr_t>(parsed);
 }
 
 }  // namespace url
diff --git a/v8 b/v8
index ac1a0c9..de7ce80 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit ac1a0c9ec71c6c9f7693767dc045f5494eb5d563
+Subproject commit de7ce8021831d4f3c4e49336623f25b2fc406fbc